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 module ocean.io.select.selector.SelectedKeysHandler; 19 20 21 import ocean.meta.types.Qualifiers; 22 23 import ocean.io.select.selector.model.ISelectedKeysHandler; 24 25 import ocean.io.select.client.model.ISelectClient; 26 27 import ocean.sys.Epoll; 28 29 import ocean.io.select.selector.EpollException; 30 31 import ocean.core.Verify; 32 33 debug (ISelectClient) import ocean.io.Stdout; 34 35 version (unittest) 36 { 37 debug = EpollFdSanity; 38 } 39 40 debug (EpollFdSanity) 41 { 42 import ocean.io.select.selector.EpollFdSanity; 43 } 44 45 /******************************************************************************/ 46 47 class SelectedKeysHandler: ISelectedKeysHandler 48 { 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 public alias int delegate ( ISelectClient client, 75 bool remove_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 protected UnregisterDg unregister; 85 86 /*************************************************************************** 87 88 Exception to throw if an error event was reported for a selected key. 89 90 ***************************************************************************/ 91 92 private EpollException e; 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 public this ( scope UnregisterDg unregister, EpollException e ) 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 override public void opCall ( epoll_event_t[] selected_set, 125 scope bool delegate (Exception) unhandled_exception_hook ) 126 { 127 foreach (key; selected_set) 128 { 129 // EpollSelectDispatcher.unregister may invalidate the 130 // 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 final protected void handleSelectedKey ( epoll_event_t key, 156 scope bool delegate (Exception) unhandled_exception_hook ) 157 { 158 debug (EpollFdSanity) 159 { 160 auto registration = FdObjEpollData.decode(key.data.u64); 161 ISelectClient client = cast (ISelectClient) registration.obj; 162 163 verify(client !is null); 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 else 169 { 170 ISelectClient client = 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 have 176 // already been unregistered (presumably deliberately), as a side- 177 // effect of handling previous clients, so we don't unregister them 178 // again or call their finalizers. 179 if ( client.is_registered ) 180 { 181 bool unregister_key = true, 182 error = false; 183 184 try 185 { 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 (Exception e) 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 !is null 200 && cast(EpollException)e is null) 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 private void checkKeyError ( ISelectClient client, Epoll.Event events ) 252 { 253 if (events & events.EPOLLERR) 254 { 255 this.e.set(client.error_code).append(" -- error event reported for "); 256 client.fmtInfo((cstring chunk) {this.e.append(chunk);}); 257 throw this.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 final protected void unregisterAndFinalize ( ISelectClient client, 275 ISelectClient.FinalizeStatus status ) 276 { 277 this.unregister(client); 278 279 try 280 { 281 client.finalize(status); 282 } 283 catch ( Exception e ) 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 private void clientError ( ISelectClient client, Epoll.Event events, Exception e ) 314 { 315 debug (ISelectClient) 316 { 317 // FIXME: printing on separate lines for now as a workaround for a 318 // dmd bug with varargs 319 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 private static void logEvents ( ISelectClient client, epoll_event_t.Event events ) 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 private static void logHandled ( ISelectClient client, bool unregister_key ) 373 { 374 if ( unregister_key ) 375 { 376 Stderr.formatln("{} :: Handled, unregistering fd", client); 377 } 378 else 379 { 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 private static void logException ( ISelectClient client, Exception e ) 397 { 398 // FIXME: printing on separate lines for now as a workaround 399 // for a dmd bug with varargs 400 401 version (none) 402 { 403 Stderr.formatln("{} :: ISelectClient handle exception: '{}' @{}:{}", 404 client, e.message(), e.file, e.line); 405 } 406 else 407 { 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 }