1 /******************************************************************************* 2 copyright: Copyright (c) 2006 Juan Jose Comellas. All rights reserved 3 license: BSD style: $(LICENSE) 4 author: Juan Jose Comellas $(EMAIL juanjo@comellas.com.ar) 5 *******************************************************************************/ 6 7 module tango.io.selector.EpollSelector; 8 9 10 version (linux) 11 { 12 public import tango.io.model.IConduit; 13 public import tango.io.selector.model.ISelector; 14 15 private import tango.io.selector.AbstractSelector; 16 private import tango.sys.Common; 17 private import tango.sys.linux.linux; 18 private import tango.stdc.errno; 19 20 debug (selector) 21 private import tango.io.Stdout; 22 23 24 /** 25 * Selector that uses the Linux epoll* family of system calls. 26 * 27 * This selector is the best option when dealing with large amounts of 28 * conduits under Linux. It will scale much better than any of the other 29 * options (PollSelector, SelectSelector). For small amounts of conduits 30 * (n < 20) the PollSelector will probably be more performant. 31 * 32 * See_Also: ISelector, AbstractSelector 33 * 34 * Examples: 35 * --- 36 * import tango.io.selector.EpollSelector; 37 * import tango.net.device.Socket; 38 * import tango.io.Stdout; 39 * 40 * SocketConduit conduit1; 41 * SocketConduit conduit2; 42 * EpollSelector selector = new EpollSelector(); 43 * MyClass object1 = new MyClass(); 44 * MyClass object2 = new MyClass(); 45 * uint eventCount; 46 * 47 * // Initialize the selector assuming that it will deal with 10 conduits and 48 * // will receive 3 events per invocation to the select() method. 49 * selector.open(10, 3); 50 * 51 * selector.register(conduit1, Event.Read, object1); 52 * selector.register(conduit2, Event.Write, object2); 53 * 54 * eventCount = selector.select(); 55 * 56 * if (eventCount > 0) 57 * { 58 * char[16] buffer; 59 * int count; 60 * 61 * foreach (SelectionKey key; selector.selectedSet()) 62 * { 63 * if (key.isReadable()) 64 * { 65 * count = (cast(SocketConduit) key.conduit).read(buffer); 66 * if (count != IConduit.Eof) 67 * { 68 * Stdout.format("Received '{0}' from peer\n", buffer[0..count]); 69 * selector.register(key.conduit, Event.Write, key.attachment); 70 * } 71 * else 72 * { 73 * selector.unregister(key.conduit); 74 * key.conduit.close(); 75 * } 76 * } 77 * 78 * if (key.isWritable()) 79 * { 80 * count = (cast(SocketConduit) key.conduit).write("MESSAGE"); 81 * if (count != IConduit.Eof) 82 * { 83 * Stdout("Sent 'MESSAGE' to peer"); 84 * selector.register(key.conduit, Event.Read, key.attachment); 85 * } 86 * else 87 * { 88 * selector.unregister(key.conduit); 89 * key.conduit.close(); 90 * } 91 * } 92 * 93 * if (key.isError() || key.isHangup() || key.isInvalidHandle()) 94 * { 95 * selector.unregister(key.conduit); 96 * key.conduit.close(); 97 * } 98 * } 99 * } 100 * 101 * selector.close(); 102 * --- 103 */ 104 public class EpollSelector: AbstractSelector 105 { 106 /** 107 * Alias for the select() method as we're not reimplementing it in 108 * this class. 109 */ 110 alias AbstractSelector.select select; 111 112 /** 113 * Default number of SelectionKey's that will be handled by the 114 * EpollSelector. 115 */ 116 public enum uint DefaultSize = 64; 117 /** 118 * Default maximum number of events that will be received per 119 * invocation to select(). 120 */ 121 public enum uint DefaultMaxEvents = 16; 122 123 124 /** Map to associate the conduit handles with their selection keys */ 125 private SelectionKey[ISelectable.Handle] _keys; 126 /** File descriptor returned by the epoll_create() system call. */ 127 private int _epfd = -1; 128 /** 129 * Array of events that is filled by epoll_wait() inside the call 130 * to select(). 131 */ 132 private epoll_event[] _events; 133 /** 134 * Persistent ISelectionSet-impl. 135 */ 136 private ISelectionSet _selectionSetIface; 137 /** Number of events resulting from the call to select() */ 138 private int _eventCount = 0; 139 140 141 /** 142 * Destructor 143 */ 144 ~this() 145 { 146 // Make sure that we release the epoll file descriptor once this 147 // object is garbage collected. 148 close(); 149 } 150 151 /** 152 * Open the epoll selector, makes a call to epoll_create() 153 * 154 * Params: 155 * size = maximum amount of conduits that will be registered; 156 * it will grow dynamically if needed. 157 * maxEvents = maximum amount of conduit events that will be 158 * returned in the selection set per call to select(); 159 * this limit is enforced by this selector. 160 * 161 * Throws: 162 * SelectorException if there are not enough resources to open the 163 * selector (e.g. not enough file handles or memory available). 164 */ 165 override public void open(uint size = DefaultSize, uint maxEvents = DefaultMaxEvents) 166 in 167 { 168 assert(size > 0); 169 assert(maxEvents > 0); 170 } 171 body 172 { 173 _events = new epoll_event[maxEvents]; 174 _selectionSetIface = new EpollSelectionSet; 175 176 _epfd = epoll_create(cast(int) size); 177 if (_epfd < 0) 178 { 179 checkErrno(__FILE__, __LINE__); 180 } 181 } 182 183 /** 184 * Close the selector, releasing the file descriptor that had been 185 * created in the previous call to open(). 186 * 187 * Remarks: 188 * It can be called multiple times without harmful side-effects. 189 */ 190 override public void close() 191 { 192 if (_epfd >= 0) 193 { 194 .close(_epfd); 195 _epfd = -1; 196 } 197 _events = null; 198 _eventCount = 0; 199 } 200 /** 201 * Return the number of keys resulting from the registration of a conduit 202 * to the selector. 203 */ 204 override public size_t count() 205 { 206 return _keys.length; 207 } 208 209 210 /** 211 * Associate a conduit to the selector and track specific I/O events. 212 * If a conduit is already associated, changes the events and 213 * attachment. 214 * 215 * Params: 216 * conduit = conduit that will be associated to the selector; 217 * must be a valid conduit (i.e. not null and open). 218 * events = bit mask of Event values that represent the events 219 * that will be tracked for the conduit. 220 * attachment = optional object with application-specific data that 221 * will be available when an event is triggered for the 222 * conduit 223 * 224 * Throws: 225 * RegisteredConduitException if the conduit had already been 226 * registered to the selector; SelectorException if there are not 227 * enough resources to add the conduit to the selector. 228 * 229 * Examples: 230 * --- 231 * selector.register(conduit, Event.Read | Event.Write, object); 232 * --- 233 */ 234 override public void register(ISelectable conduit, Event events, Object attachment = null) 235 in 236 { 237 assert(conduit !is null && conduit.fileHandle() >= 0); 238 } 239 body 240 { 241 auto key = conduit.fileHandle() in _keys; 242 243 if (key !is null) 244 { 245 epoll_event event; 246 247 key.events = events; 248 key.attachment = attachment; 249 250 event.events = events; 251 event.data.ptr = cast(void*) key; 252 253 if (epoll_ctl(_epfd, EPOLL_CTL_MOD, conduit.fileHandle(), &event) != 0) 254 { 255 checkErrno(__FILE__, __LINE__); 256 } 257 } 258 else 259 { 260 epoll_event event; 261 SelectionKey newkey = SelectionKey(conduit, events, attachment); 262 263 event.events = events; 264 265 // We associate the selection key to the epoll_event to be 266 // able to retrieve it efficiently when we get events for 267 // this handle. 268 // We keep the keys in a map to make sure that the key is not 269 // garbage collected while there is still a reference to it in 270 // an epoll_event. This also allows to to efficiently find the 271 // key corresponding to a handle in methods where this 272 // association is not provided automatically. 273 _keys[conduit.fileHandle()] = newkey; 274 auto x = conduit.fileHandle in _keys; 275 event.data.ptr = cast(void*) x; 276 if (epoll_ctl(_epfd, EPOLL_CTL_ADD, conduit.fileHandle(), &event) != 0) 277 { 278 // failed, remove the file descriptor from the keys array, 279 // and throw an error. 280 _keys.remove(conduit.fileHandle); 281 checkErrno(__FILE__, __LINE__); 282 } 283 } 284 } 285 286 /** 287 * Remove a conduit from the selector. 288 * 289 * Params: 290 * conduit = conduit that had been previously associated to the 291 * selector; it can be null. 292 * 293 * Remarks: 294 * Unregistering a null conduit is allowed and no exception is thrown 295 * if this happens. 296 * 297 * Throws: 298 * UnregisteredConduitException if the conduit had not been previously 299 * registered to the selector; SelectorException if there are not 300 * enough resources to remove the conduit registration. 301 */ 302 override public void unregister(ISelectable conduit) 303 { 304 if (conduit !is null) 305 { 306 if (epoll_ctl(_epfd, EPOLL_CTL_DEL, conduit.fileHandle(), null) == 0) 307 { 308 _keys.remove(conduit.fileHandle()); 309 } 310 else 311 { 312 int errorCode = errno; 313 314 switch ( errorCode ) 315 { 316 case EBADF: goto case; 317 case EPERM: goto case; 318 case ENOENT: 319 _keys.remove(conduit.fileHandle()); 320 break; 321 322 default: 323 break; 324 } 325 checkErrno(__FILE__, __LINE__); 326 } 327 } 328 } 329 330 /** 331 * Wait for I/O events from the registered conduits for a specified 332 * amount of time. 333 * 334 * Params: 335 * timeout = TimeSpan with the maximum amount of time that the 336 * selector will wait for events from the conduits; the 337 * amount of time is relative to the current system time 338 * (i.e. just the number of milliseconds that the selector 339 * has to wait for the events). 340 * 341 * Returns: 342 * The amount of conduits that have received events; 0 if no conduits 343 * have received events within the specified timeout; and -1 if the 344 * wakeup() method has been called from another thread. 345 * 346 * Throws: 347 * InterruptedSystemCallException if the underlying system call was 348 * interrupted by a signal and the 'restartInterruptedSystemCall' 349 * property was set to false; SelectorException if there were no 350 * resources available to wait for events from the conduits. 351 */ 352 override public int select(TimeSpan timeout) 353 { 354 int to = (timeout != TimeSpan.max ? cast(int) timeout.millis : -1); 355 356 while (true) 357 { 358 // FIXME: add support for the wakeup() call. 359 _eventCount = epoll_wait(_epfd, _events.ptr, cast(int)_events.length, to); 360 if (_eventCount >= 0) 361 { 362 break; 363 } 364 else 365 { 366 if (errno != EINTR || !_restartInterruptedSystemCall) 367 { 368 checkErrno(__FILE__, __LINE__); 369 } 370 debug (selector) 371 Stdout("--- Restarting epoll_wait() after being interrupted by a signal\n"); 372 } 373 } 374 return _eventCount; 375 } 376 377 /** 378 * Class used to hold the list of Conduits that have received events. 379 * See_Also: ISelectionSet 380 */ 381 protected class EpollSelectionSet: ISelectionSet 382 { 383 public size_t length() 384 { 385 return _events.length; 386 } 387 388 /** 389 * Iterate over all the Conduits that have received events. 390 */ 391 public int opApply(scope int delegate(ref SelectionKey) dg) 392 { 393 int rc = 0; 394 SelectionKey key; 395 396 debug (selector) 397 Stdout.format("--- EpollSelectionSet.opApply() ({0} events)\n", _events.length); 398 399 foreach (epoll_event event; _events[0.._eventCount]) 400 { 401 // Only invoke the delegate if there is an event for the conduit. 402 if (event.events != 0) 403 { 404 key = *(cast(SelectionKey *)event.data.ptr); 405 key.events = cast(Event) event.events; 406 407 debug (selector) 408 Stdout.format("--- Event 0x{0:x} for handle {1}\n", 409 cast(uint) event.events, cast(int) key.conduit.fileHandle()); 410 411 rc = dg(key); 412 if (rc != 0) 413 { 414 break; 415 } 416 } 417 } 418 return rc; 419 } 420 } 421 422 /** 423 * Return the selection set resulting from the call to any of the 424 * select() methods. 425 * 426 * Remarks: 427 * If the call to select() was unsuccessful or it did not return any 428 * events, the returned value will be null. 429 */ 430 override public ISelectionSet selectedSet() 431 { 432 return (_eventCount > 0 ? _selectionSetIface : null); 433 } 434 435 /** 436 * Return the selection key resulting from the registration of a conduit 437 * to the selector. 438 * 439 * Remarks: 440 * If the conduit is not registered to the selector the returned 441 * value will SelectionKey.init. No exception will be thrown by this 442 * method. 443 */ 444 override public SelectionKey key(ISelectable conduit) 445 { 446 if(conduit !is null) 447 { 448 if(auto k = conduit.fileHandle in _keys) 449 { 450 return *k; 451 } 452 } 453 return SelectionKey.init; 454 } 455 456 /** 457 * Iterate through the currently registered selection keys. Note that 458 * you should not erase or add any items from the selector while 459 * iterating, although you can register existing conduits again. 460 */ 461 int opApply(scope int delegate(ref SelectionKey) dg) 462 { 463 int result = 0; 464 foreach(v; _keys) 465 { 466 if((result = dg(v)) != 0) 467 break; 468 } 469 return result; 470 } 471 472 unittest 473 { 474 } 475 } 476 } 477