1 /*******************************************************************************
2 3 Handles a set of selected epoll keys. A selected epoll key is an event
4 reported by epoll bundled with its context; the context is the ISelectClient
5 object that contains the file descriptor and the event handler method.
6 7 Copyright:
8 Copyright (c) 2009-2016 dunnhumby Germany GmbH.
9 All rights reserved.
10 11 License:
12 Boost Software License Version 1.0. See LICENSE_BOOST.txt for details.
13 Alternatively, this file may be distributed under the terms of the Tango
14 3-Clause BSD License (see LICENSE_BSD.txt for details).
15 16 *******************************************************************************/17 18 moduleocean.io.select.selector.SelectedKeysHandler;
19 20 21 importocean.meta.types.Qualifiers;
22 23 importocean.io.select.selector.model.ISelectedKeysHandler;
24 25 importocean.io.select.client.model.ISelectClient;
26 27 importocean.sys.Epoll;
28 29 importocean.io.select.selector.EpollException;
30 31 importocean.core.Verify;
32 33 debug (ISelectClient) importocean.io.Stdout;
34 35 version (unittest)
36 {
37 debug = EpollFdSanity;
38 }
39 40 debug (EpollFdSanity)
41 {
42 importocean.io.select.selector.EpollFdSanity;
43 }
44 45 /******************************************************************************/46 47 classSelectedKeysHandler: ISelectedKeysHandler48 {
49 /***************************************************************************
50 51 Type alias of a callback delegate to remove a client registration. Does
52 not fail/throw if the client is not registered.
53 54 Params:
55 client = client to unregister
56 remove_from_selected_set = if true, removes the client from the
57 selected_set that we could be currently iterating. This will allow
58 safely destroying client instance which may be registered.
59 60 Should return:
61 0 if everything worked as expected or the error code (errno) as a
62 warning on minor errors, that is, everything except ENOMEM (out of
63 memory) and EINVAL (invalid epoll file descriptor or epoll_ctl()
64 opcode).
65 ENOENT is a minor error that happens regularly when the client was
66 unexpectedly unregistered as it happens when its file descriptor is
67 closed.
68 69 Should throw:
70 EpollException on the fatal errors ENOMEM and EINVAL.
71 72 ***************************************************************************/73 74 publicaliasintdelegate ( ISelectClientclient,
75 boolremove_from_selected_set = false ) UnregisterDg;
76 77 /***************************************************************************
78 79 Callback delegate to remove a client registration, see the description
80 for the type alias above.
81 82 ***************************************************************************/83 84 protectedUnregisterDgunregister;
85 86 /***************************************************************************
87 88 Exception to throw if an error event was reported for a selected key.
89 90 ***************************************************************************/91 92 privateEpollExceptione;
93 94 /***************************************************************************
95 96 Constructor.
97 98 Params:
99 unregister = callback delegate to remove a client registration, must
100 be available during the lifetime of this instance
101 e = exception to keep and throw if an error event was reported for
102 a selected key
103 104 ***************************************************************************/105 106 publicthis ( scopeUnregisterDgunregister, EpollExceptione )
107 {
108 this.unregister = unregister;
109 this.e = e;
110 }
111 112 /***************************************************************************
113 114 Handles the clients in selected_set.
115 116 Params:
117 selected_set = the result list of epoll_wait()
118 unhandled_exception_hook = if not null, will be called each time
119 event call results in unhandled exception. May both rethrow
120 and consume exception instance after processing it.
121 122 ***************************************************************************/123 124 overridepublicvoidopCall ( epoll_event_t[] selected_set,
125 scopebooldelegate (Exception) unhandled_exception_hook )
126 {
127 foreach (key; selected_set)
128 {
129 // EpollSelectDispatcher.unregister may invalidate the130 // slot in the array during the unregistration of the client,131 // so we'll skip all these.132 if (key != epoll_event_t.init)
133 {
134 this.handleSelectedKey(key, unhandled_exception_hook);
135 }
136 }
137 }
138 139 /***************************************************************************
140 141 Handles key by calling its handle() method and unregisters it if the
142 handle() call either returns false or throws an exception. In the latter
143 case the exception thrown is reported to the client by calling its
144 error() method.
145 146 Params:
147 key = an epoll key which contains a client to be handled and the
148 reported event
149 unhandled_exception_hook = if not null, will be called each time
150 event call results in unhandled exception. May both rethrow
151 and consume exception instance after processing it.
152 153 **************************************************************************/154 155 finalprotectedvoidhandleSelectedKey ( epoll_event_tkey,
156 scopebooldelegate (Exception) unhandled_exception_hook )
157 {
158 debug (EpollFdSanity)
159 {
160 autoregistration = FdObjEpollData.decode(key.data.u64);
161 ISelectClientclient = cast (ISelectClient) registration.obj;
162 163 verify(client !isnull);
164 verify(registration.verifyFd(client.fileHandle()),
165 "Warning: the ISelectClient.fileHandle() doesn't match the "166 ~ "original fd this ISelectClient was registered for");
167 }
168 else169 {
170 ISelectClientclient = cast (ISelectClient) key.data.ptr;
171 }
172 173 debug ( ISelectClient ) this.logEvents(client, key.events);
174 175 // Only handle clients which are registered. Clients may have176 // already been unregistered (presumably deliberately), as a side-177 // effect of handling previous clients, so we don't unregister them178 // again or call their finalizers.179 if ( client.is_registered )
180 {
181 boolunregister_key = true,
182 error = false;
183 184 try185 {
186 this.checkKeyError(client, key.events);
187 188 unregister_key = !client.handle(key.events);
189 190 debug (ISelectClient) this.logHandled( client, unregister_key);
191 }
192 catch (Exceptione)
193 {
194 debug (ISelectClient) this.logException(client, e);
195 196 this.clientError(client, key.events, e);
197 error = true;
198 199 if ( unhandled_exception_hook !isnull200 && cast(EpollException)eisnull)
201 {
202 unhandled_exception_hook(e);
203 }
204 }
205 206 if (unregister_key)
207 {
208 this.unregisterAndFinalize(client,
209 error? client.FinalizeStatus.Error :
210 client.FinalizeStatus.Success);
211 }
212 }
213 }
214 215 /***************************************************************************
216 217 Checks if a selection key error has occurred by checking events and
218 querying a socket error.
219 220 Hangup states are not checked here, for the following reasons:
221 1. The hangup event is not an error on its own and may be expected
222 to happen, e.g. when short term connections are used. In that
223 case it is also possible and expected that hangup combined with
224 the read event when the remote closed the connection after having
225 data sent, and that data have not been read from the socket yet.
226 2. Experience shows that, when epoll reports a combination of read
227 and hangup event, it will keep reporting that combination even if
228 there are actually no data pending to read from the socket. In
229 that case the only way of determining whether there are data
230 pending is calling read() and comparing the return value against
231 EOF. An application that relies on an exception thrown here will
232 then run into an endless turbo event loop.
233 3. Only the application knows whether hangup events are expected or
234 exceptions. If it expects them, it may want its handler to be
235 invoked which will not happen if checkKeyError() throws an
236 exception. If it treats hangup events as exceptions, it will want
237 an exception to be thrown even if it was combined with a read or
238 write event.
239 240 Params:
241 client = client for which an event was reported
242 events = reported events
243 244 Throws:
245 EpollException if events contains an error code. The exception
246 thrown, which is an ErrnoIOException and an IOException, contains
247 the errno code as reported by client.error_code.
248 249 **************************************************************************/250 251 privatevoidcheckKeyError ( ISelectClientclient, Epoll.Eventevents )
252 {
253 if (events & events.EPOLLERR)
254 {
255 this.e.set(client.error_code).append(" -- error event reported for ");
256 client.fmtInfo((cstringchunk) {this.e.append(chunk);});
257 throwthis.e;
258 }
259 }
260 261 /***************************************************************************
262 263 Unregisters and finalizes a select client. Any errors which occur while
264 calling the client's finalizer are caught and reported to the client's
265 error() method (see clientError(), below).
266 267 Params:
268 client = client to finalize
269 status = finalize status to report to the client (e.g. success or
270 error)
271 272 ***************************************************************************/273 274 finalprotectedvoidunregisterAndFinalize ( ISelectClientclient,
275 ISelectClient.FinalizeStatusstatus )
276 {
277 this.unregister(client);
278 279 try280 {
281 client.finalize(status);
282 }
283 catch ( Exceptione )
284 {
285 debug (ISelectClient)
286 {
287 Stderr.format("{} :: Error while finalizing client: '{}'",
288 client, e.message()).flush();
289 if ( e.line )
290 {
291 Stderr.format("@ {}:{}", e.file, e.line);
292 }
293 Stderr.formatln("");
294 }
295 this.clientError(client, Epoll.Event.None, e);
296 }
297 }
298 299 /***************************************************************************
300 301 Called when an exception is thrown while handling a client (either the
302 handle() or finalize() method).
303 304 Calls the client's error() method, and in debug builds ouputs a message.
305 306 Params:
307 client = client which threw e
308 events = epoll events which fired for client
309 e = exception thrown by client.handle() or client.finalize()
310 311 **************************************************************************/312 313 privatevoidclientError ( ISelectClientclient, Epoll.Eventevents, Exceptione )
314 {
315 debug (ISelectClient)
316 {
317 // FIXME: printing on separate lines for now as a workaround for a318 // dmd bug with varargs319 Stderr.formatln("{} :: Error during handle:", client);
320 Stderr.formatln(" '{}'", e.message()).flush();
321 if ( e.line )
322 {
323 Stderr.formatln(" @ {}:{}", e.file, e.line).flush();
324 }
325 }
326 327 client.error(e, events);
328 }
329 330 /***************************************************************************
331 332 Debug console output functions.
333 334 ***************************************************************************/335 336 debug (ISelectClient):
337 338 /***************************************************************************
339 340 Logs that events were reported for client.
341 342 Params:
343 client = select client for which events were reported
344 events = events reported for client
345 346 ***************************************************************************/347 348 privatestaticvoidlogEvents ( ISelectClientclient, epoll_event_t.Eventevents )
349 {
350 Stderr.format("{} :: Epoll firing with events ", client);
351 foreach ( event, name; epoll_event_t.event_to_name )
352 {
353 if ( events & event )
354 {
355 Stderr.format("{}", name);
356 }
357 }
358 Stderr.formatln("").flush();
359 }
360 361 /***************************************************************************
362 363 Logs that client was handled.
364 365 Params:
366 client = handled client
367 unregister_key = true if the client is unregistered or false if it
368 stays registered
369 370 ***************************************************************************/371 372 privatestaticvoidlogHandled ( ISelectClientclient, boolunregister_key )
373 {
374 if ( unregister_key )
375 {
376 Stderr.formatln("{} :: Handled, unregistering fd", client);
377 }
378 else379 {
380 Stderr.formatln("{} :: Handled, leaving fd registered", client);
381 }
382 Stderr.flush();
383 }
384 385 /***************************************************************************
386 387 Logs that an exception was thrown while handing client. This includes
388 an error event reported by epoll.
389 390 Params:
391 client = client that caused an error
392 e = caught exception
393 394 ***************************************************************************/395 396 privatestaticvoidlogException ( ISelectClientclient, Exceptione )
397 {
398 // FIXME: printing on separate lines for now as a workaround399 // for a dmd bug with varargs400 401 version (none)
402 {
403 Stderr.formatln("{} :: ISelectClient handle exception: '{}' @{}:{}",
404 client, e.message(), e.file, e.line);
405 }
406 else407 {
408 Stderr.formatln("{} :: ISelectClient handle exception:", client);
409 Stderr.formatln(" '{}'", e.message());
410 Stderr.formatln(" @{}:{}", e.file, e.line);
411 }
412 Stderr.flush();
413 }
414 }