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 05 Socket Functions */
14 #include "anyp/PortCfg.h"
15 #include "comm/Connection.h"
16 #include "comm/Loops.h"
21 #include "mgr/Registration.h"
22 #include "SquidConfig.h"
23 #include "StatCounters.h"
31 /* Needed for poll() on Linux at least */
34 #define POLLRDNORM POLLIN
37 #define POLLWRNORM POLLOUT
41 static int MAX_POLL_TIME
= 1000; /* see also Comm::QuickPollRequired() */
44 #define howmany(x, y) (((x)+((y)-1))/(y))
49 #define FD_MASK_BYTES sizeof(fd_mask)
50 #define FD_MASK_BITS (FD_MASK_BYTES*NBBY)
53 static int fdIsTcpListen(int fd
);
54 static int fdIsUdpListen(int fd
);
55 static int fdIsDns(int fd
);
56 static OBJH commIncomingStats
;
57 static int comm_check_incoming_poll_handlers(int nfds
, int *fds
);
58 static void comm_poll_dns_incoming(void);
61 Comm::SetSelect(int fd
, unsigned int type
, PF
* handler
, void *client_data
, time_t timeout
)
63 fde
*F
= &fd_table
[fd
];
65 assert(F
->flags
.open
|| (!handler
&& !client_data
&& !timeout
));
66 debugs(5, 5, "FD " << fd
<< ", type=" << type
<<
67 ", handler=" << handler
<< ", client_data=" << client_data
<<
68 ", timeout=" << timeout
);
70 if (type
& COMM_SELECT_READ
) {
71 F
->read_handler
= handler
;
72 F
->read_data
= client_data
;
75 if (type
& COMM_SELECT_WRITE
) {
76 F
->write_handler
= handler
;
77 F
->write_data
= client_data
;
81 F
->timeout
= squid_curtime
+ timeout
;
87 if (icpIncomingConn
!= nullptr && icpIncomingConn
->fd
== fd
)
90 if (icpOutgoingConn
!= nullptr && icpOutgoingConn
->fd
== fd
)
102 if (fd
== DnsSocketB
)
109 fdIsTcpListen(int fd
)
111 for (AnyP::PortCfgPointer s
= HttpPortList
; s
!= nullptr; s
= s
->next
) {
112 if (s
->listenConn
!= nullptr && s
->listenConn
->fd
== fd
)
120 comm_check_incoming_poll_handlers(int nfds
, int *fds
)
127 struct pollfd pfds
[3 + MAXTCPLISTENPORTS
];
128 incoming_sockets_accepted
= 0;
130 for (i
= npfds
= 0; i
< nfds
; ++i
) {
135 if (fd_table
[fd
].read_handler
)
136 events
|= POLLRDNORM
;
138 if (fd_table
[fd
].write_handler
)
139 events
|= POLLWRNORM
;
143 pfds
[npfds
].events
= events
;
144 pfds
[npfds
].revents
= 0;
153 ++ statCounter
.syscalls
.selects
;
155 if (poll(pfds
, npfds
, 0) < 1)
156 return incoming_sockets_accepted
;
158 for (i
= 0; i
< npfds
; ++i
) {
161 if (((revents
= pfds
[i
].revents
) == 0) || ((fd
= pfds
[i
].fd
) == -1))
164 if (revents
& (POLLRDNORM
| POLLIN
| POLLHUP
| POLLERR
)) {
165 if ((hdl
= fd_table
[fd
].read_handler
)) {
166 fd_table
[fd
].read_handler
= nullptr;
167 hdl(fd
, fd_table
[fd
].read_data
);
168 } else if (pfds
[i
].events
& POLLRDNORM
)
169 debugs(5, DBG_IMPORTANT
, "comm_poll_incoming: FD " << fd
<< " NULL read handler");
172 if (revents
& (POLLWRNORM
| POLLOUT
| POLLHUP
| POLLERR
)) {
173 if ((hdl
= fd_table
[fd
].write_handler
)) {
174 fd_table
[fd
].write_handler
= nullptr;
175 hdl(fd
, fd_table
[fd
].write_data
);
176 } else if (pfds
[i
].events
& POLLWRNORM
)
177 debugs(5, DBG_IMPORTANT
, "comm_poll_incoming: FD " << fd
<< " NULL write_handler");
181 return incoming_sockets_accepted
;
185 comm_poll_udp_incoming(void)
190 if (Comm::IsConnOpen(icpIncomingConn
)) {
191 fds
[nfds
] = icpIncomingConn
->fd
;
195 if (icpIncomingConn
!= icpOutgoingConn
&& Comm::IsConnOpen(icpOutgoingConn
)) {
196 fds
[nfds
] = icpOutgoingConn
->fd
;
200 if (statCounter
.comm_udp
.startPolling(nfds
)) {
201 auto n
= comm_check_incoming_poll_handlers(nfds
, fds
);
202 statCounter
.comm_udp
.finishPolling(n
, Config
.comm_incoming
.udp
);
207 comm_poll_tcp_incoming(void)
210 int fds
[MAXTCPLISTENPORTS
];
212 // XXX: only poll sockets that won't be deferred. But how do we identify them?
214 for (AnyP::PortCfgPointer s
= HttpPortList
; s
!= nullptr; s
= s
->next
) {
215 if (Comm::IsConnOpen(s
->listenConn
)) {
216 fds
[nfds
] = s
->listenConn
->fd
;
221 if (statCounter
.comm_tcp
.startPolling(nfds
)) {
222 auto n
= comm_check_incoming_poll_handlers(nfds
, fds
);
223 statCounter
.comm_tcp
.finishPolling(n
, Config
.comm_incoming
.tcp
);
227 /* poll all sockets; call handlers for those that are ready. */
229 Comm::DoSelect(int msec
)
231 struct pollfd pfds
[SQUID_MAXFD
];
237 unsigned long npending
;
239 int calldns
= 0, calludp
= 0, calltcp
= 0;
240 double timeout
= current_dtime
+ (msec
/ 1000.0);
245 start
= current_dtime
;
247 if (statCounter
.comm_udp
.check())
248 comm_poll_udp_incoming();
250 if (statCounter
.comm_dns
.check())
251 comm_poll_dns_incoming();
253 if (statCounter
.comm_tcp
.check())
254 comm_poll_tcp_incoming();
256 calldns
= calludp
= calltcp
= 0;
262 maxfd
= Biggest_FD
+ 1;
264 for (int i
= 0; i
< maxfd
; ++i
) {
267 /* Check each open socket for a handler. */
269 if (fd_table
[i
].read_handler
)
270 events
|= POLLRDNORM
;
272 if (fd_table
[i
].write_handler
)
273 events
|= POLLWRNORM
;
277 pfds
[nfds
].events
= events
;
278 pfds
[nfds
].revents
= 0;
281 if ((events
& POLLRDNORM
) && fd_table
[i
].flags
.read_pending
)
289 if (msec
> MAX_POLL_TIME
)
290 msec
= MAX_POLL_TIME
;
294 * Note that this will only ever trigger when there are no log files
295 * and stdout/err/in are all closed too.
297 if (nfds
== 0 && npending
== 0) {
299 return Comm::SHUTDOWN
;
305 ++ statCounter
.syscalls
.selects
;
306 num
= poll(pfds
, nfds
, msec
);
308 ++ statCounter
.select_loops
;
310 if (num
>= 0 || npending
> 0)
313 if (ignoreErrno(xerrno
))
316 debugs(5, DBG_CRITICAL
, MYNAME
<< "poll failure: " << xstrerr(xerrno
));
318 assert(xerrno
!= EINVAL
);
320 return Comm::COMM_ERROR
;
327 debugs(5, num
? 5 : 8, "comm_poll: " << num
<< "+" << npending
<< " FDs ready");
328 statCounter
.select_fds_hist
.count(num
);
330 if (num
== 0 && npending
== 0)
333 /* scan each socket but the accept socket. Poll this
334 * more frequently to minimize losses due to the 5 connect
337 for (size_t loopIndex
= 0; loopIndex
< nfds
; ++loopIndex
) {
339 int revents
= pfds
[loopIndex
].revents
;
340 fd
= pfds
[loopIndex
].fd
;
345 if (fd_table
[fd
].flags
.read_pending
)
351 if (fdIsUdpListen(fd
)) {
361 if (fdIsTcpListen(fd
)) {
368 if (revents
& (POLLRDNORM
| POLLIN
| POLLHUP
| POLLERR
)) {
369 debugs(5, 6, "comm_poll: FD " << fd
<< " ready for reading");
371 if ((hdl
= F
->read_handler
)) {
372 F
->read_handler
= nullptr;
373 hdl(fd
, F
->read_data
);
374 ++ statCounter
.select_fds
;
376 if (statCounter
.comm_udp
.check())
377 comm_poll_udp_incoming();
379 if (statCounter
.comm_dns
.check())
380 comm_poll_dns_incoming();
382 if (statCounter
.comm_tcp
.check())
383 comm_poll_tcp_incoming();
387 if (revents
& (POLLWRNORM
| POLLOUT
| POLLHUP
| POLLERR
)) {
388 debugs(5, 6, "comm_poll: FD " << fd
<< " ready for writing");
390 if ((hdl
= F
->write_handler
)) {
391 F
->write_handler
= nullptr;
392 hdl(fd
, F
->write_data
);
393 ++ statCounter
.select_fds
;
395 if (statCounter
.comm_udp
.check())
396 comm_poll_udp_incoming();
398 if (statCounter
.comm_dns
.check())
399 comm_poll_dns_incoming();
401 if (statCounter
.comm_tcp
.check())
402 comm_poll_tcp_incoming();
406 if (revents
& POLLNVAL
) {
407 AsyncCall::Pointer ch
;
408 debugs(5, DBG_CRITICAL
, "WARNING: FD " << fd
<< " has handlers, but it's invalid.");
409 debugs(5, DBG_CRITICAL
, "FD " << fd
<< " is a " << fdTypeStr
[F
->type
]);
410 debugs(5, DBG_CRITICAL
, "--> " << F
->desc
);
411 debugs(5, DBG_CRITICAL
, "tmout:" << F
->timeoutHandler
<< "read:" <<
412 F
->read_handler
<< " write:" << F
->write_handler
);
414 for (ch
= F
->closeHandler
; ch
!= nullptr; ch
= ch
->Next())
415 debugs(5, DBG_CRITICAL
, " close handler: " << ch
);
417 if (F
->closeHandler
!= nullptr) {
418 commCallCloseHandlers(fd
);
419 } else if (F
->timeoutHandler
!= nullptr) {
420 debugs(5, DBG_CRITICAL
, "comm_poll: Calling Timeout Handler");
421 ScheduleCallHere(F
->timeoutHandler
);
424 F
->closeHandler
= nullptr;
425 F
->timeoutHandler
= nullptr;
426 F
->read_handler
= nullptr;
427 F
->write_handler
= nullptr;
435 comm_poll_udp_incoming();
438 comm_poll_dns_incoming();
441 comm_poll_tcp_incoming();
445 statCounter
.select_time
+= (current_dtime
- start
);
448 } while (timeout
> current_dtime
);
450 debugs(5, 8, "comm_poll: time out: " << squid_curtime
<< ".");
452 return Comm::TIMEOUT
;
456 comm_poll_dns_incoming(void)
461 if (DnsSocketA
>= 0) {
462 fds
[nfds
] = DnsSocketA
;
466 if (DnsSocketB
>= 0) {
467 fds
[nfds
] = DnsSocketB
;
471 if (statCounter
.comm_dns
.startPolling(nfds
)) {
472 auto n
= comm_check_incoming_poll_handlers(nfds
, fds
);
473 statCounter
.comm_dns
.finishPolling(n
, Config
.comm_incoming
.dns
);
478 commPollRegisterWithCacheManager(void)
480 Mgr::RegisterAction("comm_poll_incoming",
481 "comm_incoming() stats",
482 commIncomingStats
, 0, 1);
486 Comm::SelectLoopInit(void)
488 commPollRegisterWithCacheManager();
492 commIncomingStats(StoreEntry
* sentry
)
494 storeAppendPrintf(sentry
, "Current incoming_udp_interval: %d\n",
495 statCounter
.comm_udp
.interval
>> Comm::Incoming::Factor
);
496 storeAppendPrintf(sentry
, "Current incoming_dns_interval: %d\n",
497 statCounter
.comm_dns
.interval
>> Comm::Incoming::Factor
);
498 storeAppendPrintf(sentry
, "Current incoming_tcp_interval: %d\n",
499 statCounter
.comm_tcp
.interval
>> Comm::Incoming::Factor
);
500 storeAppendPrintf(sentry
, "\n");
501 storeAppendPrintf(sentry
, "Histogram of events per incoming socket type\n");
502 storeAppendPrintf(sentry
, "ICP Messages handled per comm_poll_udp_incoming() call:\n");
503 statCounter
.comm_udp
.history
.dump(sentry
, statHistIntDumper
);
504 storeAppendPrintf(sentry
, "DNS Messages handled per comm_poll_dns_incoming() call:\n");
505 statCounter
.comm_dns
.history
.dump(sentry
, statHistIntDumper
);
506 storeAppendPrintf(sentry
, "HTTP Messages handled per comm_poll_tcp_incoming() call:\n");
507 statCounter
.comm_tcp
.history
.dump(sentry
, statHistIntDumper
);
510 /* Called by async-io or diskd to speed up the polling */
512 Comm::QuickPollRequired(void)
517 #endif /* USE_POLL */