Squid Web Cache master
Loading...
Searching...
No Matches
ModSelect.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 05 Socket Functions */
10
11#include "squid.h"
12
13#if USE_SELECT
14
15#include "anyp/PortCfg.h"
16#include "comm/Connection.h"
17#include "comm/Loops.h"
18#include "compat/select.h"
19#include "fde.h"
20#include "globals.h"
21#include "ICP.h"
22#include "mgr/Registration.h"
23#include "SquidConfig.h"
24#include "StatCounters.h"
25#include "StatHist.h"
26#include "Store.h"
27
28#include <cerrno>
29#if HAVE_SYS_STAT_H
30#include <sys/stat.h>
31#endif
32
33static int MAX_POLL_TIME = 1000; /* see also Comm::QuickPollRequired() */
34
35#ifndef howmany
36#define howmany(x, y) (((x)+((y)-1))/(y))
37#endif
38#ifndef NBBY
39#define NBBY 8
40#endif
41#define FD_MASK_BYTES sizeof(fd_mask)
42#define FD_MASK_BITS (FD_MASK_BYTES*NBBY)
43
44/* STATIC */
45static int examine_select(fd_set *, fd_set *);
46static int fdIsTcpListener(int fd);
47static int fdIsUdpListener(int fd);
48static int fdIsDns(int fd);
50static int comm_check_incoming_select_handlers(int nfds, int *fds);
51static void comm_select_dns_incoming(void);
52static void commUpdateReadBits(int fd, PF * handler);
53static void commUpdateWriteBits(int fd, PF * handler);
54
55static struct timeval zero_tv;
56static fd_set global_readfds;
57static fd_set global_writefds;
58static int nreadfds;
59static int nwritefds;
60
61void
62Comm::SetSelect(int fd, unsigned int type, PF * handler, void *client_data, time_t timeout)
63{
64 fde *F = &fd_table[fd];
65 assert(fd >= 0);
66 assert(F->flags.open || (!handler && !client_data && !timeout));
67 debugs(5, 5, "FD " << fd << ", type=" << type <<
68 ", handler=" << handler << ", client_data=" << client_data <<
69 ", timeout=" << timeout);
70
71 if (type & COMM_SELECT_READ) {
72 F->read_handler = handler;
73 F->read_data = client_data;
74 commUpdateReadBits(fd, handler);
75 }
76
77 if (type & COMM_SELECT_WRITE) {
78 F->write_handler = handler;
79 F->write_data = client_data;
80 commUpdateWriteBits(fd, handler);
81 }
82
83 if (timeout)
84 F->timeout = squid_curtime + timeout;
85}
86
87static int
89{
90 if (icpIncomingConn != nullptr && fd == icpIncomingConn->fd)
91 return 1;
92
93 if (icpOutgoingConn != nullptr && fd == icpOutgoingConn->fd)
94 return 1;
95
96 return 0;
97}
98
99static int
100fdIsDns(int fd)
101{
102 if (fd == DnsSocketA)
103 return 1;
104
105 if (fd == DnsSocketB)
106 return 1;
107
108 return 0;
109}
110
111static int
113{
114 for (AnyP::PortCfgPointer s = HttpPortList; s != nullptr; s = s->next) {
115 if (s->listenConn != nullptr && s->listenConn->fd == fd)
116 return 1;
117 }
118
119 return 0;
120}
121
122static int
124{
125 int i;
126 int fd;
127 int maxfd = 0;
128 PF *hdl = nullptr;
129 fd_set read_mask;
130 fd_set write_mask;
131 FD_ZERO(&read_mask);
132 FD_ZERO(&write_mask);
134
135 for (i = 0; i < nfds; ++i) {
136 fd = fds[i];
137
138 if (fd_table[fd].read_handler) {
139 FD_SET(fd, &read_mask);
140
141 if (fd > maxfd)
142 maxfd = fd;
143 }
144
145 if (fd_table[fd].write_handler) {
146 FD_SET(fd, &write_mask);
147
148 if (fd > maxfd)
149 maxfd = fd;
150 }
151 }
152
153 if (maxfd++ == 0)
154 return -1;
155
157
159
160 if (xselect(maxfd, &read_mask, &write_mask, nullptr, &zero_tv) < 1)
162
163 for (i = 0; i < nfds; ++i) {
164 fd = fds[i];
165
166 if (FD_ISSET(fd, &read_mask)) {
167 if ((hdl = fd_table[fd].read_handler) != nullptr) {
168 fd_table[fd].read_handler = nullptr;
169 commUpdateReadBits(fd, nullptr);
170 hdl(fd, fd_table[fd].read_data);
171 } else {
172 debugs(5, DBG_IMPORTANT, "comm_select_incoming: FD " << fd << " NULL read handler");
173 }
174 }
175
176 if (FD_ISSET(fd, &write_mask)) {
177 if ((hdl = fd_table[fd].write_handler) != nullptr) {
178 fd_table[fd].write_handler = nullptr;
179 commUpdateWriteBits(fd, nullptr);
180 hdl(fd, fd_table[fd].write_data);
181 } else {
182 debugs(5, DBG_IMPORTANT, "comm_select_incoming: FD " << fd << " NULL write handler");
183 }
184 }
185 }
186
188}
189
190static void
192{
193 int nfds = 0;
194 int fds[2];
195
197 fds[nfds] = icpIncomingConn->fd;
198 ++nfds;
199 }
200
202 fds[nfds] = icpOutgoingConn->fd;
203 ++nfds;
204 }
205
207 auto n = comm_check_incoming_select_handlers(nfds, fds);
209 }
210}
211
212static void
214{
215 int nfds = 0;
216 int fds[MAXTCPLISTENPORTS];
217
218 // XXX: only poll sockets that won't be deferred. But how do we identify them?
219
220 for (AnyP::PortCfgPointer s = HttpPortList; s != nullptr; s = s->next) {
221 if (Comm::IsConnOpen(s->listenConn)) {
222 fds[nfds] = s->listenConn->fd;
223 ++nfds;
224 }
225 }
226
228 auto n = comm_check_incoming_select_handlers(nfds, fds);
230 }
231}
232
233/* Select on all sockets; call handlers for those that are ready. */
235Comm::DoSelect(int msec)
236{
237 fd_set readfds;
238 fd_set pendingfds;
239 fd_set writefds;
240
241 PF *hdl = nullptr;
242 int fd;
243 int maxfd;
244 int num;
245 int pending;
246 int calldns = 0, calludp = 0, calltcp = 0;
247 int maxindex;
248 unsigned int k;
249 int j;
250 fd_mask *fdsp;
251 fd_mask *pfdsp;
252 fd_mask tmask;
253
254 struct timeval poll_time;
255 double timeout = current_dtime + (msec / 1000.0);
256 fde *F;
257
258 do {
259 double start;
261 start = current_dtime;
262
265
268
271
272 calldns = calludp = calltcp = 0;
273
274 maxfd = Biggest_FD + 1;
275
276 memcpy(&readfds, &global_readfds,
278
279 memcpy(&writefds, &global_writefds,
281
282 /* remove stalled FDs, and deal with pending descriptors */
283 pending = 0;
284
285 FD_ZERO(&pendingfds);
286
287 maxindex = howmany(maxfd, FD_MASK_BITS);
288
289 fdsp = (fd_mask *) & readfds;
290
291 for (j = 0; j < maxindex; ++j) {
292 if ((tmask = fdsp[j]) == 0)
293 continue; /* no bits here */
294
295 for (k = 0; k < FD_MASK_BITS; ++k) {
296 if (!EBIT_TEST(tmask, k))
297 continue;
298
299 /* Found a set bit */
300 fd = (j * FD_MASK_BITS) + k;
301
302 if (FD_ISSET(fd, &readfds) && fd_table[fd].flags.read_pending) {
303 FD_SET(fd, &pendingfds);
304 ++pending;
305 }
306 }
307 }
308
309 if (nreadfds + nwritefds == 0) {
311 return Comm::SHUTDOWN;
312 }
313
314 if (msec > MAX_POLL_TIME)
315 msec = MAX_POLL_TIME;
316
317 if (pending)
318 msec = 0;
319
320 for (;;) {
321 poll_time.tv_sec = msec / 1000;
322 poll_time.tv_usec = (msec % 1000) * 1000;
324 num = xselect(maxfd, &readfds, &writefds, nullptr, &poll_time);
325 int xerrno = errno;
327
328 if (num >= 0 || pending > 0)
329 break;
330
331 if (ignoreErrno(xerrno))
332 break;
333
334 debugs(5, DBG_CRITICAL, MYNAME << "select failure: " << xstrerr(xerrno));
335
336 examine_select(&readfds, &writefds);
337
338 return Comm::COMM_ERROR;
339
340 /* NOTREACHED */
341 }
342
343 if (num < 0 && !pending)
344 continue;
345
347
348 debugs(5, num ? 5 : 8, "comm_select: " << num << "+" << pending << " FDs ready");
349
351
352 if (num == 0 && pending == 0)
353 continue;
354
355 /* Scan return fd masks for ready descriptors */
356 fdsp = (fd_mask *) & readfds;
357
358 pfdsp = (fd_mask *) & pendingfds;
359
360 maxindex = howmany(maxfd, FD_MASK_BITS);
361
362 for (j = 0; j < maxindex; ++j) {
363 if ((tmask = (fdsp[j] | pfdsp[j])) == 0)
364 continue; /* no bits here */
365
366 for (k = 0; k < FD_MASK_BITS; ++k) {
367 if (tmask == 0)
368 break; /* no more bits left */
369
370 if (!EBIT_TEST(tmask, k))
371 continue;
372
373 /* Found a set bit */
374 fd = (j * FD_MASK_BITS) + k;
375
376 EBIT_CLR(tmask, k); /* this will be done */
377
378 if (fdIsUdpListener(fd)) {
379 calludp = 1;
380 continue;
381 }
382
383 if (fdIsDns(fd)) {
384 calldns = 1;
385 continue;
386 }
387
388 if (fdIsTcpListener(fd)) {
389 calltcp = 1;
390 continue;
391 }
392
393 F = &fd_table[fd];
394 debugs(5, 6, "comm_select: FD " << fd << " ready for reading");
395
396 if (nullptr == (hdl = F->read_handler))
397 (void) 0;
398 else {
399 F->read_handler = nullptr;
400 commUpdateReadBits(fd, nullptr);
401 hdl(fd, F->read_data);
403
406
409
412 }
413 }
414 }
415
416 fdsp = (fd_mask *) & writefds;
417
418 for (j = 0; j < maxindex; ++j) {
419 if ((tmask = fdsp[j]) == 0)
420 continue; /* no bits here */
421
422 for (k = 0; k < FD_MASK_BITS; ++k) {
423 if (tmask == 0)
424 break; /* no more bits left */
425
426 if (!EBIT_TEST(tmask, k))
427 continue;
428
429 /* Found a set bit */
430 fd = (j * FD_MASK_BITS) + k;
431
432 EBIT_CLR(tmask, k); /* this will be done */
433
434 if (fdIsUdpListener(fd)) {
435 calludp = 1;
436 continue;
437 }
438
439 if (fdIsDns(fd)) {
440 calldns = 1;
441 continue;
442 }
443
444 if (fdIsTcpListener(fd)) {
445 calltcp = 1;
446 continue;
447 }
448
449 F = &fd_table[fd];
450 debugs(5, 6, "comm_select: FD " << fd << " ready for writing");
451
452 if ((hdl = F->write_handler)) {
453 F->write_handler = nullptr;
454 commUpdateWriteBits(fd, nullptr);
455 hdl(fd, F->write_data);
457
460
463
466 }
467 }
468 }
469
470 if (calludp)
472
473 if (calldns)
475
476 if (calltcp)
478
480
482
483 return Comm::OK;
484 } while (timeout > current_dtime);
485 debugs(5, 8, "comm_select: time out: " << squid_curtime);
486
487 return Comm::TIMEOUT;
488}
489
490static void
492{
493 int nfds = 0;
494 int fds[3];
495
496 if (DnsSocketA >= 0) {
497 fds[nfds] = DnsSocketA;
498 ++nfds;
499 }
500
501 if (DnsSocketB >= 0) {
502 fds[nfds] = DnsSocketB;
503 ++nfds;
504 }
505
507 auto n = comm_check_incoming_select_handlers(nfds, fds);
509 }
510}
511
512void
514{
515 zero_tv.tv_sec = 0;
516 zero_tv.tv_usec = 0;
517 FD_ZERO(&global_readfds);
518 FD_ZERO(&global_writefds);
519 nreadfds = nwritefds = 0;
520
521 Mgr::RegisterAction("comm_select_incoming",
522 "comm_incoming() stats",
523 commIncomingStats, 0, 1);
524}
525
526/*
527 * examine_select - debug routine.
528 *
529 * I spend the day chasing this core dump that occurs when both the client
530 * and the server side of a cache fetch simultaneoulsy abort the
531 * connection. While I haven't really studied the code to figure out how
532 * it happens, the snippet below may prevent the cache from exiting:
533 *
534 * Call this from where the select loop fails.
535 */
536static int
537examine_select(fd_set * readfds, fd_set * writefds)
538{
539 int fd = 0;
540 fd_set read_x;
541 fd_set write_x;
542
543 struct timeval tv;
544 AsyncCall::Pointer ch = nullptr;
545 fde *F = nullptr;
546
547 struct stat sb;
548 debugs(5, DBG_CRITICAL, "examine_select: Examining open file descriptors...");
549
550 for (fd = 0; fd < Squid_MaxFD; ++fd) {
551 FD_ZERO(&read_x);
552 FD_ZERO(&write_x);
553 tv.tv_sec = tv.tv_usec = 0;
554
555 if (FD_ISSET(fd, readfds))
556 FD_SET(fd, &read_x);
557 else if (FD_ISSET(fd, writefds))
558 FD_SET(fd, &write_x);
559 else
560 continue;
561
563 errno = 0;
564
565 if (!fstat(fd, &sb)) {
566 debugs(5, 5, "FD " << fd << " is valid.");
567 continue;
568 }
569 int xerrno = errno;
570
571 F = &fd_table[fd];
572 debugs(5, DBG_CRITICAL, "fstat(FD " << fd << "): " << xstrerr(xerrno));
573 debugs(5, DBG_CRITICAL, "WARNING: FD " << fd << " has handlers, but it's invalid.");
574 debugs(5, DBG_CRITICAL, "FD " << fd << " is a " << fdTypeStr[F->type] << " called '" << F->desc << "'");
575 debugs(5, DBG_CRITICAL, "tmout:" << F->timeoutHandler << " read:" << F->read_handler << " write:" << F->write_handler);
576
577 for (ch = F->closeHandler; ch != nullptr; ch = ch->Next())
578 debugs(5, DBG_CRITICAL, " close handler: " << ch);
579
580 if (F->closeHandler != nullptr) {
582 } else if (F->timeoutHandler != nullptr) {
583 debugs(5, DBG_CRITICAL, "examine_select: Calling Timeout Handler");
585 }
586
587 F->closeHandler = nullptr;
588 F->timeoutHandler = nullptr;
589 F->read_handler = nullptr;
590 F->write_handler = nullptr;
591 FD_CLR(fd, readfds);
592 FD_CLR(fd, writefds);
593 }
594
595 return 0;
596}
597
598static void
600{
601 storeAppendPrintf(sentry, "Current incoming_udp_interval: %d\n",
603 storeAppendPrintf(sentry, "Current incoming_dns_interval: %d\n",
605 storeAppendPrintf(sentry, "Current incoming_tcp_interval: %d\n",
607 storeAppendPrintf(sentry, "\n");
608 storeAppendPrintf(sentry, "Histogram of events per incoming socket type\n");
609 storeAppendPrintf(sentry, "ICP Messages handled per comm_select_udp_incoming() call:\n");
611 storeAppendPrintf(sentry, "DNS Messages handled per comm_select_dns_incoming() call:\n");
613 storeAppendPrintf(sentry, "HTTP Messages handled per comm_select_tcp_incoming() call:\n");
615}
616
617void
618commUpdateReadBits(int fd, PF * handler)
619{
620 if (handler && !FD_ISSET(fd, &global_readfds)) {
621 FD_SET(fd, &global_readfds);
622 ++nreadfds;
623 } else if (!handler && FD_ISSET(fd, &global_readfds)) {
624 FD_CLR(fd, &global_readfds);
625 --nreadfds;
626 }
627}
628
629void
630commUpdateWriteBits(int fd, PF * handler)
631{
632 if (handler && !FD_ISSET(fd, &global_writefds)) {
633 FD_SET(fd, &global_writefds);
634 ++nwritefds;
635 } else if (!handler && FD_ISSET(fd, &global_writefds)) {
636 FD_CLR(fd, &global_writefds);
637 --nwritefds;
638 }
639}
640
641/* Called by async-io or diskd to speed up the polling */
642void
644{
645 MAX_POLL_TIME = 10;
646}
647
648#endif /* USE_SELECT */
649
#define ScheduleCallHere(call)
Definition AsyncCall.h:166
static fd_set global_readfds
Definition ModSelect.cc:56
static int fdIsTcpListener(int fd)
Definition ModSelect.cc:112
static int fdIsDns(int fd)
Definition ModSelect.cc:100
static void commUpdateReadBits(int fd, PF *handler)
Definition ModSelect.cc:618
#define FD_MASK_BITS
Definition ModSelect.cc:42
static void comm_select_udp_incoming(void)
Definition ModSelect.cc:191
static int nreadfds
Definition ModSelect.cc:58
#define howmany(x, y)
Definition ModSelect.cc:36
static int nwritefds
Definition ModSelect.cc:59
static int MAX_POLL_TIME
Definition ModSelect.cc:33
#define FD_MASK_BYTES
Definition ModSelect.cc:41
static void comm_select_tcp_incoming(void)
Definition ModSelect.cc:213
static struct timeval zero_tv
Definition ModSelect.cc:55
static int examine_select(fd_set *, fd_set *)
Definition ModSelect.cc:537
static int fdIsUdpListener(int fd)
Definition ModSelect.cc:88
static int comm_check_incoming_select_handlers(int nfds, int *fds)
Definition ModSelect.cc:123
static OBJH commIncomingStats
Definition ModSelect.cc:49
static void commUpdateWriteBits(int fd, PF *handler)
Definition ModSelect.cc:630
static fd_set global_writefds
Definition ModSelect.cc:57
static void comm_select_dns_incoming(void)
Definition ModSelect.cc:491
time_t squid_curtime
AnyP::PortCfgPointer HttpPortList
list of Squid http(s)_port configured
Definition PortCfg.cc:22
#define MAXTCPLISTENPORTS
Definition PortCfg.h:86
class SquidConfig Config
StatCounters statCounter
StatHistBinDumper statHistIntDumper
Definition StatHist.h:119
#define assert(EX)
Definition assert.h:17
bool check()
Definition Incoming.h:86
bool startPolling(int n)
Definition Incoming.h:68
void finishPolling(int, SquidConfig::CommIncoming::Measure &)
Definition Incoming.cc:15
StatHist history
Definition Incoming.h:104
static const int Factor
Definition Incoming.h:50
struct SquidConfig::CommIncoming comm_incoming
double select_time
StatHist select_fds_hist
Comm::Incoming comm_udp
unsigned long int select_loops
struct StatCounters::@112 syscalls
Comm::Incoming comm_dns
Comm::Incoming comm_tcp
void count(double val)
Definition StatHist.cc:55
void dump(StoreEntry *sentry, StatHistBinDumper *bd) const
Definition StatHist.cc:171
Definition fde.h:52
unsigned int type
Definition fde.h:105
char desc[FD_DESC_SZ]
Definition fde.h:115
AsyncCall::Pointer timeoutHandler
Definition fde.h:153
PF * read_handler
Definition fde.h:149
void * write_data
Definition fde.h:152
struct fde::_fde_flags flags
time_t timeout
Definition fde.h:154
AsyncCall::Pointer closeHandler
Definition fde.h:157
void * read_data
Definition fde.h:150
PF * write_handler
Definition fde.h:151
void PF(int, void *)
Definition forward.h:18
void commCallCloseHandlers(int fd)
Definition comm.cc:744
int ignoreErrno(int ierrno)
Definition comm.cc:1407
#define MYNAME
Definition Stream.h:219
#define DBG_IMPORTANT
Definition Stream.h:38
#define debugs(SECTION, LEVEL, CONTENT)
Definition Stream.h:192
#define DBG_CRITICAL
Definition Stream.h:37
#define COMM_SELECT_READ
Definition defines.h:24
#define EBIT_CLR(flag, bit)
Definition defines.h:66
#define COMM_SELECT_WRITE
Definition defines.h:25
#define EBIT_TEST(flag, bit)
Definition defines.h:67
const char * fdTypeStr[]
Definition fd.cc:34
#define fd_table
Definition fde.h:189
int DnsSocketB
int DnsSocketA
int shutting_down
int Squid_MaxFD
int Biggest_FD
int incoming_sockets_accepted
Comm::ConnectionPointer icpOutgoingConn
Definition icp_v2.cc:101
Comm::ConnectionPointer icpIncomingConn
Definition icp_v2.cc:99
void OBJH(StoreEntry *)
Definition forward.h:44
void QuickPollRequired(void)
bool IsConnOpen(const Comm::ConnectionPointer &conn)
Definition Connection.cc:27
Flag
Definition Flag.h:15
@ SHUTDOWN
Definition Flag.h:19
@ OK
Definition Flag.h:16
@ TIMEOUT
Definition Flag.h:18
@ COMM_ERROR
Definition Flag.h:17
Comm::Flag DoSelect(int)
Do poll and trigger callback functions as appropriate.
void SelectLoopInit(void)
Initialize the module on Squid startup.
void SetSelect(int, unsigned int, PF *, void *, time_t)
Mark an FD to be watched for its IO status.
void RegisterAction(char const *action, char const *desc, OBJH *handler, Protected, Atomic, Format)
int xselect(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout)
POSIX select(2) equivalent.
Definition select.h:22
void storeAppendPrintf(StoreEntry *e, const char *fmt,...)
Definition store.cc:855
struct SquidConfig::CommIncoming::Measure udp
struct SquidConfig::CommIncoming::Measure dns
struct SquidConfig::CommIncoming::Measure tcp
bool open
Definition fde.h:118
double current_dtime
the current UNIX time in seconds (with microsecond precision)
time_t getCurrentTime() STUB_RETVAL(0) int tvSubUsec(struct timeval
unsigned long fd_mask
Definition types.h:133
const char * xstrerr(int error)
Definition xstrerror.cc:83