]>
Commit | Line | Data |
---|---|---|
1 | /* | |
2 | * Copyright (C) 1996-2021 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 | /* | |
12 | * The idea for this came from these two websites: | |
13 | * http://www.xmailserver.org/linux-patches/nio-improve.html | |
14 | * http://www.kegel.com/c10k.html | |
15 | * | |
16 | * This is to support the epoll sysctl being added to the linux 2.5 | |
17 | * kernel tree. The new sys_epoll is an event based poller without | |
18 | * most of the fuss of rtsignals. | |
19 | * | |
20 | * -- David Nicklay <dnicklay@web.turner.com> | |
21 | */ | |
22 | ||
23 | /* | |
24 | * XXX Currently not implemented / supported by this module XXX | |
25 | * | |
26 | * - delay pools | |
27 | * - deferred reads | |
28 | * | |
29 | */ | |
30 | ||
31 | #include "squid.h" | |
32 | ||
33 | #if USE_EPOLL | |
34 | ||
35 | #include "base/CodeContext.h" | |
36 | #include "comm/Loops.h" | |
37 | #include "fde.h" | |
38 | #include "globals.h" | |
39 | #include "mgr/Registration.h" | |
40 | #include "profiler/Profiler.h" | |
41 | #include "SquidTime.h" | |
42 | #include "StatCounters.h" | |
43 | #include "StatHist.h" | |
44 | #include "Store.h" | |
45 | ||
46 | #define DEBUG_EPOLL 0 | |
47 | ||
48 | #include <cerrno> | |
49 | #if HAVE_SYS_EPOLL_H | |
50 | #include <sys/epoll.h> | |
51 | #endif | |
52 | ||
53 | static int kdpfd = -1; | |
54 | static int max_poll_time = 1000; | |
55 | ||
56 | static struct epoll_event *pevents; | |
57 | ||
58 | static void commEPollRegisterWithCacheManager(void); | |
59 | ||
60 | /* XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX */ | |
61 | /* Public functions */ | |
62 | ||
63 | /* | |
64 | * This is a needed exported function which will be called to initialise | |
65 | * the network loop code. | |
66 | */ | |
67 | void | |
68 | Comm::SelectLoopInit(void) | |
69 | { | |
70 | pevents = (struct epoll_event *) xmalloc(SQUID_MAXFD * sizeof(struct epoll_event)); | |
71 | ||
72 | if (!pevents) { | |
73 | int xerrno = errno; | |
74 | fatalf("comm_select_init: xmalloc() failed: %s\n", xstrerr(xerrno)); | |
75 | } | |
76 | ||
77 | kdpfd = epoll_create(SQUID_MAXFD); | |
78 | ||
79 | if (kdpfd < 0) { | |
80 | int xerrno = errno; | |
81 | fatalf("comm_select_init: epoll_create(): %s\n", xstrerr(xerrno)); | |
82 | } | |
83 | ||
84 | commEPollRegisterWithCacheManager(); | |
85 | } | |
86 | ||
87 | static const char* epolltype_atoi(int x) | |
88 | { | |
89 | switch (x) { | |
90 | ||
91 | case EPOLL_CTL_ADD: | |
92 | return "EPOLL_CTL_ADD"; | |
93 | ||
94 | case EPOLL_CTL_DEL: | |
95 | return "EPOLL_CTL_DEL"; | |
96 | ||
97 | case EPOLL_CTL_MOD: | |
98 | return "EPOLL_CTL_MOD"; | |
99 | ||
100 | default: | |
101 | return "UNKNOWN_EPOLLCTL_OP"; | |
102 | } | |
103 | } | |
104 | ||
105 | /** | |
106 | * This is a needed exported function which will be called to register | |
107 | * and deregister interest in a pending IO state for a given FD. | |
108 | */ | |
109 | void | |
110 | Comm::SetSelect(int fd, unsigned int type, PF * handler, void *client_data, time_t timeout) | |
111 | { | |
112 | fde *F = &fd_table[fd]; | |
113 | int epoll_ctl_type = 0; | |
114 | ||
115 | assert(fd >= 0); | |
116 | debugs(5, 5, HERE << "FD " << fd << ", type=" << type << | |
117 | ", handler=" << handler << ", client_data=" << client_data << | |
118 | ", timeout=" << timeout); | |
119 | ||
120 | struct epoll_event ev; | |
121 | memset(&ev, 0, sizeof(ev)); | |
122 | ev.data.fd = fd; | |
123 | ||
124 | if (!F->flags.open) { | |
125 | epoll_ctl(kdpfd, EPOLL_CTL_DEL, fd, &ev); | |
126 | return; | |
127 | } | |
128 | ||
129 | // If read is an interest | |
130 | ||
131 | if (type & COMM_SELECT_READ) { | |
132 | if (handler) { | |
133 | // Hack to keep the events flowing if there is data immediately ready | |
134 | if (F->flags.read_pending) | |
135 | ev.events |= EPOLLOUT; | |
136 | ev.events |= EPOLLIN; | |
137 | } | |
138 | ||
139 | F->read_handler = handler; | |
140 | ||
141 | F->read_data = client_data; | |
142 | ||
143 | // Otherwise, use previously stored value | |
144 | } else if (F->epoll_state & EPOLLIN) { | |
145 | ev.events |= EPOLLIN; | |
146 | } | |
147 | ||
148 | // If write is an interest | |
149 | if (type & COMM_SELECT_WRITE) { | |
150 | if (handler) | |
151 | ev.events |= EPOLLOUT; | |
152 | ||
153 | F->write_handler = handler; | |
154 | ||
155 | F->write_data = client_data; | |
156 | ||
157 | // Otherwise, use previously stored value | |
158 | } else if (F->epoll_state & EPOLLOUT) { | |
159 | ev.events |= EPOLLOUT; | |
160 | } | |
161 | ||
162 | if (ev.events) | |
163 | ev.events |= EPOLLHUP | EPOLLERR; | |
164 | ||
165 | if (ev.events != F->epoll_state) { | |
166 | if (F->epoll_state) // already monitoring something. | |
167 | epoll_ctl_type = ev.events ? EPOLL_CTL_MOD : EPOLL_CTL_DEL; | |
168 | else | |
169 | epoll_ctl_type = EPOLL_CTL_ADD; | |
170 | ||
171 | F->epoll_state = ev.events; | |
172 | ||
173 | if (epoll_ctl(kdpfd, epoll_ctl_type, fd, &ev) < 0) { | |
174 | int xerrno = errno; | |
175 | debugs(5, DEBUG_EPOLL ? 0 : 8, "epoll_ctl(," << epolltype_atoi(epoll_ctl_type) << | |
176 | ",,): failed on FD " << fd << ": " << xstrerr(xerrno)); | |
177 | } | |
178 | } | |
179 | ||
180 | if (timeout) | |
181 | F->timeout = squid_curtime + timeout; | |
182 | ||
183 | if (timeout || handler) // all non-cleanup requests | |
184 | F->codeContext = CodeContext::Current(); // TODO: Avoid clearing if set? | |
185 | else if (!ev.events) // full cleanup: no more FD-associated work expected | |
186 | F->codeContext = nullptr; | |
187 | // else: direction-specific/timeout cleanup requests preserve F->codeContext | |
188 | } | |
189 | ||
190 | static void commIncomingStats(StoreEntry * sentry); | |
191 | ||
192 | static void | |
193 | commEPollRegisterWithCacheManager(void) | |
194 | { | |
195 | Mgr::RegisterAction("comm_epoll_incoming", | |
196 | "comm_incoming() stats", | |
197 | commIncomingStats, 0, 1); | |
198 | } | |
199 | ||
200 | static void | |
201 | commIncomingStats(StoreEntry * sentry) | |
202 | { | |
203 | StatCounters *f = &statCounter; | |
204 | storeAppendPrintf(sentry, "Total number of epoll(2) loops: %ld\n", statCounter.select_loops); | |
205 | storeAppendPrintf(sentry, "Histogram of returned filedescriptors\n"); | |
206 | f->select_fds_hist.dump(sentry, statHistIntDumper); | |
207 | } | |
208 | ||
209 | /** | |
210 | * Check all connections for new connections and input data that is to be | |
211 | * processed. Also check for connections with data queued and whether we can | |
212 | * write it out. | |
213 | * | |
214 | * Called to do the new-style IO, courtesy of of squid (like most of this | |
215 | * new IO code). This routine handles the stuff we've hidden in | |
216 | * comm_setselect and fd_table[] and calls callbacks for IO ready | |
217 | * events. | |
218 | */ | |
219 | Comm::Flag | |
220 | Comm::DoSelect(int msec) | |
221 | { | |
222 | int num, i,fd; | |
223 | fde *F; | |
224 | PF *hdl; | |
225 | ||
226 | struct epoll_event *cevents; | |
227 | ||
228 | PROF_start(comm_check_incoming); | |
229 | ||
230 | if (msec > max_poll_time) | |
231 | msec = max_poll_time; | |
232 | ||
233 | for (;;) { | |
234 | num = epoll_wait(kdpfd, pevents, SQUID_MAXFD, msec); | |
235 | ++ statCounter.select_loops; | |
236 | ||
237 | if (num >= 0) | |
238 | break; | |
239 | ||
240 | if (ignoreErrno(errno)) | |
241 | break; | |
242 | ||
243 | getCurrentTime(); | |
244 | ||
245 | PROF_stop(comm_check_incoming); | |
246 | ||
247 | return Comm::COMM_ERROR; | |
248 | } | |
249 | ||
250 | PROF_stop(comm_check_incoming); | |
251 | getCurrentTime(); | |
252 | ||
253 | statCounter.select_fds_hist.count(num); | |
254 | ||
255 | if (num == 0) | |
256 | return Comm::TIMEOUT; /* No error.. */ | |
257 | ||
258 | PROF_start(comm_handle_ready_fd); | |
259 | ||
260 | for (i = 0, cevents = pevents; i < num; ++i, ++cevents) { | |
261 | fd = cevents->data.fd; | |
262 | F = &fd_table[fd]; | |
263 | CodeContext::Reset(F->codeContext); | |
264 | debugs(5, DEBUG_EPOLL ? 0 : 8, HERE << "got FD " << fd << " events=" << | |
265 | std::hex << cevents->events << " monitoring=" << F->epoll_state << | |
266 | " F->read_handler=" << F->read_handler << " F->write_handler=" << F->write_handler); | |
267 | ||
268 | // TODO: add EPOLLPRI?? | |
269 | ||
270 | if (cevents->events & (EPOLLIN|EPOLLHUP|EPOLLERR) || F->flags.read_pending) { | |
271 | if ((hdl = F->read_handler) != NULL) { | |
272 | debugs(5, DEBUG_EPOLL ? 0 : 8, HERE << "Calling read handler on FD " << fd); | |
273 | PROF_start(comm_write_handler); | |
274 | F->read_handler = NULL; | |
275 | hdl(fd, F->read_data); | |
276 | PROF_stop(comm_write_handler); | |
277 | ++ statCounter.select_fds; | |
278 | } else { | |
279 | debugs(5, DEBUG_EPOLL ? 0 : 8, HERE << "no read handler for FD " << fd); | |
280 | // remove interest since no handler exist for this event. | |
281 | SetSelect(fd, COMM_SELECT_READ, NULL, NULL, 0); | |
282 | } | |
283 | } | |
284 | ||
285 | if (cevents->events & (EPOLLOUT|EPOLLHUP|EPOLLERR)) { | |
286 | if ((hdl = F->write_handler) != NULL) { | |
287 | debugs(5, DEBUG_EPOLL ? 0 : 8, HERE << "Calling write handler on FD " << fd); | |
288 | PROF_start(comm_read_handler); | |
289 | F->write_handler = NULL; | |
290 | hdl(fd, F->write_data); | |
291 | PROF_stop(comm_read_handler); | |
292 | ++ statCounter.select_fds; | |
293 | } else { | |
294 | debugs(5, DEBUG_EPOLL ? 0 : 8, HERE << "no write handler for FD " << fd); | |
295 | // remove interest since no handler exist for this event. | |
296 | SetSelect(fd, COMM_SELECT_WRITE, NULL, NULL, 0); | |
297 | } | |
298 | } | |
299 | } | |
300 | ||
301 | CodeContext::Reset(); | |
302 | ||
303 | PROF_stop(comm_handle_ready_fd); | |
304 | ||
305 | return Comm::OK; | |
306 | } | |
307 | ||
308 | void | |
309 | Comm::QuickPollRequired(void) | |
310 | { | |
311 | max_poll_time = 10; | |
312 | } | |
313 | ||
314 | #endif /* USE_EPOLL */ | |
315 |