]> git.ipfire.org Git - thirdparty/squid.git/blob - src/pconn.cc
Make removeFD report failures and prevent non-idle FD being closed
[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 "Store.h"
38 #include "comm.h"
39 #include "pconn.h"
40 #include "fde.h"
41
42 #define PCONN_FDS_SZ 8 /* pconn set size, increase for better memcache hit rate */
43
44 static MemAllocator *pconn_fds_pool = NULL;
45 PconnModule * PconnModule::instance = NULL;
46 CBDATA_CLASS_INIT(IdleConnList);
47
48 /* ========== IdleConnList ============================================ */
49
50 IdleConnList::IdleConnList(const char *key, PconnPool *thePool) : parent(thePool)
51 {
52 hash.key = xstrdup(key);
53 nfds_alloc = PCONN_FDS_SZ;
54 nfds = 0;
55 fds = (int *)pconn_fds_pool->alloc();
56 }
57
58 IdleConnList::~IdleConnList()
59 {
60
61 parent->unlinkList(this);
62
63 if (nfds_alloc == PCONN_FDS_SZ)
64 pconn_fds_pool->freeOne(fds);
65 else
66 xfree(fds);
67
68 xfree(hash.key);
69 }
70
71 int
72 IdleConnList::findFDIndex (int fd)
73 {
74 int index;
75
76 for (index = nfds - 1; index >= 0; --index) {
77 if (fds[index] == fd)
78 return index;
79 }
80
81 return -1;
82 }
83
84 bool
85 IdleConnList::removeFD(int fd)
86 {
87 int index = findFDIndex(fd);
88 if (index < 0) {
89 debugs(48, 2, "IdleConnList::removeFD: FD " << fd << " NOT FOUND!");
90 return false;
91 }
92 debugs(48, 3, "IdleConnList::removeFD: found FD " << fd << " at index " << index);
93
94 for (; index < nfds - 1; index++)
95 fds[index] = fds[index + 1];
96
97 if (--nfds == 0) {
98 debugs(48, 3, "IdleConnList::removeFD: deleting " << hashKeyStr(&hash));
99 delete this;
100 }
101 return true;
102 }
103
104 void
105 IdleConnList::clearHandlers(int fd)
106 {
107 comm_read_cancel(fd, IdleConnList::read, this);
108 commSetTimeout(fd, -1, NULL, NULL);
109 }
110
111 void
112 IdleConnList::push(int fd)
113 {
114 if (nfds == nfds_alloc) {
115 debugs(48, 3, "IdleConnList::push: growing FD array");
116 nfds_alloc <<= 1;
117 int *old = fds;
118 fds = (int *)xmalloc(nfds_alloc * sizeof(int));
119 xmemcpy(fds, old, nfds * sizeof(int));
120
121 if (nfds == PCONN_FDS_SZ)
122 pconn_fds_pool->freeOne(old);
123 else
124 xfree(old);
125 }
126
127 fds[nfds++] = fd;
128 comm_read(fd, fakeReadBuf, sizeof(fakeReadBuf), IdleConnList::read, this);
129 commSetTimeout(fd, Config.Timeout.pconn, IdleConnList::timeout, this);
130 }
131
132 /*
133 * XXX this routine isn't terribly efficient - if there's a pending
134 * read event (which signifies the fd will close in the next IO loop!)
135 * we ignore the FD and move onto the next one. This means, as an example,
136 * if we have a lot of FDs open to a very popular server and we get a bunch
137 * of requests JUST as they timeout (say, it shuts down) we'll be wasting
138 * quite a bit of CPU. Just keep it in mind.
139 */
140 int
141 IdleConnList::findUseableFD()
142 {
143 assert(nfds);
144
145 for (int i=nfds-1; i>=0; i--) {
146 if (!comm_has_pending_read_callback(fds[i])) {
147 return fds[i];
148 }
149 }
150
151 return -1;
152 }
153
154 void
155 IdleConnList::read(int fd, char *buf, size_t len, comm_err_t flag, int xerrno, void *data)
156 {
157 debugs(48, 3, "IdleConnList::read: " << len << " bytes from FD " << fd);
158
159 if (flag == COMM_ERR_CLOSING) {
160 /* Bail out early on COMM_ERR_CLOSING - close handlers will tidy up for us */
161 return;
162 }
163
164 IdleConnList *list = (IdleConnList *) data;
165 if (list->removeFD(fd)) /* might delete list */
166 comm_close(fd);
167 }
168
169 void
170 IdleConnList::timeout(int fd, void *data)
171 {
172 debugs(48, 3, "IdleConnList::timeout: FD " << fd);
173 IdleConnList *list = (IdleConnList *) data;
174 if (list->removeFD(fd)) /* might delete list */
175 comm_close(fd);
176 }
177
178 /* ========== PconnPool PRIVATE FUNCTIONS ============================================ */
179
180 const char *
181 PconnPool::key(const char *host, u_short port, const char *domain, Ip::Address &client_address)
182 {
183 LOCAL_ARRAY(char, buf, SQUIDHOSTNAMELEN * 3 + 10);
184 char ntoabuf[MAX_IPSTRLEN];
185
186 if (domain && !client_address.IsAnyAddr())
187 snprintf(buf, SQUIDHOSTNAMELEN * 3 + 10, "%s:%d-%s/%s", host, (int) port, client_address.NtoA(ntoabuf,MAX_IPSTRLEN), domain);
188 else if (domain && client_address.IsAnyAddr())
189 snprintf(buf, SQUIDHOSTNAMELEN * 3 + 10, "%s:%d/%s", host, (int) port, domain);
190 else if ((!domain) && !client_address.IsAnyAddr())
191 snprintf(buf, SQUIDHOSTNAMELEN * 3 + 10, "%s:%d-%s", host, (int) port, client_address.NtoA(ntoabuf,MAX_IPSTRLEN));
192 else
193 snprintf(buf, SQUIDHOSTNAMELEN * 3 + 10, "%s:%d", host, (int) port);
194
195 debugs(48,6,"PconnPool::key(" << (host?host:"(no host!)") << "," << port << "," << (domain?domain:"(no domain)") << "," << client_address << "is {" << buf << "}" );
196 return buf;
197 }
198
199 void
200 PconnPool::dumpHist(StoreEntry * e)
201 {
202 int i;
203 storeAppendPrintf(e,
204 "%s persistent connection counts:\n"
205 "\n"
206 "\treq/\n"
207 "\tconn count\n"
208 "\t---- ---------\n",
209 descr);
210
211 for (i = 0; i < PCONN_HIST_SZ; i++) {
212 if (hist[i] == 0)
213 continue;
214
215 storeAppendPrintf(e, "\t%4d %9d\n", i, hist[i]);
216 }
217 }
218
219 void
220 PconnPool::dumpHash(StoreEntry *e)
221 {
222 int i;
223 hash_link *walker = NULL;
224 hash_table *hid = table;
225 hash_first(hid);
226
227 for (i = 0, walker = hid->next; walker; walker = hash_next(hid)) {
228 storeAppendPrintf(e, "\t item %5d: %s\n", i++, (char *)(walker->key));
229 }
230 }
231
232 /* ========== PconnPool PUBLIC FUNCTIONS ============================================ */
233
234 PconnPool::PconnPool(const char *aDescr) : table(NULL), descr(aDescr)
235 {
236 int i;
237 table = hash_create((HASHCMP *) strcmp, 229, hash_string);
238
239 for (i = 0; i < PCONN_HIST_SZ; i++)
240 hist[i] = 0;
241
242 PconnModule::GetInstance()->add(this);
243 }
244
245 PconnPool::~PconnPool()
246 {
247 descr = NULL;
248 hashFreeMemory(table);
249 }
250
251 void
252 PconnPool::push(int fd, const char *host, u_short port, const char *domain, Ip::Address &client_address)
253 {
254 IdleConnList *list;
255 const char *aKey;
256 LOCAL_ARRAY(char, desc, FD_DESC_SZ);
257
258 if (fdUsageHigh()) {
259 debugs(48, 3, "PconnPool::push: Not many unused FDs");
260 comm_close(fd);
261 return;
262 } else if (shutting_down) {
263 comm_close(fd);
264 debugs(48, 3, "PconnPool::push: Squid is shutting down. Refusing to do anything");
265 return;
266 }
267
268 aKey = key(host, port, domain, client_address);
269
270 list = (IdleConnList *) hash_lookup(table, aKey);
271
272 if (list == NULL) {
273 list = new IdleConnList(aKey, this);
274 debugs(48, 3, "PconnPool::push: new IdleConnList for {" << hashKeyStr(&list->hash) << "}" );
275 hash_join(table, &list->hash);
276 } else {
277 debugs(48, 3, "PconnPool::push: found IdleConnList for {" << hashKeyStr(&list->hash) << "}" );
278 }
279
280 list->push(fd);
281
282 assert(!comm_has_incomplete_write(fd));
283 snprintf(desc, FD_DESC_SZ, "%s idle connection", host);
284 fd_note(fd, desc);
285 debugs(48, 3, "PconnPool::push: pushed FD " << fd << " for " << aKey);
286 }
287
288 /**
289 * Return a pconn fd for host:port if available and retriable.
290 * Otherwise, return -1.
291 *
292 * We close available persistent connection if the caller transaction is not
293 * retriable to avoid having a growing number of open connections when many
294 * transactions create persistent connections but are not retriable.
295 */
296 int
297 PconnPool::pop(const char *host, u_short port, const char *domain, Ip::Address &client_address, bool isRetriable)
298 {
299 const char * aKey = key(host, port, domain, client_address);
300
301 IdleConnList *list = (IdleConnList *)hash_lookup(table, aKey);
302 if (list == NULL) {
303 debugs(48, 3, "PconnPool::pop: lookup for key {" << aKey << "} failed.");
304 return -1;
305 } else {
306 debugs(48, 3, "PconnPool::pop: found " << hashKeyStr(&list->hash) << (isRetriable?"(to use)":"(to kill)") );
307 }
308
309 int fd = list->findUseableFD(); // search from the end. skip pending reads.
310
311 if (fd >= 0) {
312 list->clearHandlers(fd);
313
314 /* might delete list */
315 if (list->removeFD(fd) && !isRetriable) {
316 comm_close(fd);
317 return -1;
318 }
319 }
320
321 return fd;
322 }
323
324 void
325 PconnPool::unlinkList(IdleConnList *list) const
326 {
327 hash_remove_link(table, &list->hash);
328 }
329
330 void
331 PconnPool::count(int uses)
332 {
333 if (uses >= PCONN_HIST_SZ)
334 uses = PCONN_HIST_SZ - 1;
335
336 hist[uses]++;
337 }
338
339 /* ========== PconnModule ============================================ */
340
341 /*
342 * This simple class exists only for the cache manager
343 */
344
345 PconnModule::PconnModule() : pools(NULL), poolCount(0)
346 {
347 pools = (PconnPool **) xcalloc(MAX_NUM_PCONN_POOLS, sizeof(*pools));
348 pconn_fds_pool = memPoolCreate("pconn_fds", PCONN_FDS_SZ * sizeof(int));
349 debugs(48, 0, "persistent connection module initialized");
350 registerWithCacheManager();
351 }
352
353 PconnModule *
354 PconnModule::GetInstance()
355 {
356 if (instance == NULL)
357 instance = new PconnModule;
358
359 return instance;
360 }
361
362 void
363 PconnModule::registerWithCacheManager(void)
364 {
365 CacheManager::GetInstance()->
366 registerAction("pconn",
367 "Persistent Connection Utilization Histograms",
368 DumpWrapper, 0, 1);
369 }
370
371 void
372
373 PconnModule::add
374 (PconnPool *aPool)
375 {
376 assert(poolCount < MAX_NUM_PCONN_POOLS);
377 *(pools+poolCount) = aPool;
378 poolCount++;
379 }
380
381 void
382 PconnModule::dump(StoreEntry *e)
383 {
384 int i;
385
386 for (i = 0; i < poolCount; i++) {
387 storeAppendPrintf(e, "\n Pool %d Stats\n", i);
388 (*(pools+i))->dumpHist(e);
389 storeAppendPrintf(e, "\n Pool %d Hash Table\n",i);
390 (*(pools+i))->dumpHash(e);
391 }
392 }
393
394 void
395 PconnModule::DumpWrapper(StoreEntry *e)
396 {
397 PconnModule::GetInstance()->dump(e);
398 }