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.PollSelector; 8 9 version (Posix) 10 { 11 public import tango.io.model.IConduit; 12 public import tango.io.selector.model.ISelector; 13 14 private import tango.io.selector.AbstractSelector; 15 private import tango.io.selector.SelectorException; 16 private import tango.sys.Common; 17 private import tango.stdc.errno; 18 19 version (linux) 20 private import tango.sys.linux.linux; 21 22 debug (selector) 23 private import tango.io.Stdout; 24 25 26 /** 27 * Selector that uses the poll() system call to receive I/O events for 28 * the registered conduits. To use this class you would normally do 29 * something like this: 30 * 31 * Examples: 32 * --- 33 * import tango.io.selector.PollSelector; 34 * 35 * Socket socket; 36 * ISelector selector = new PollSelector(); 37 * 38 * selector.open(100, 10); 39 * 40 * // Register to read from socket 41 * selector.register(socket, Event.Read); 42 * 43 * int eventCount = selector.select(0.1); // 0.1 seconds 44 * if (eventCount > 0) 45 * { 46 * // We can now read from the socket 47 * socket.read(); 48 * } 49 * else if (eventCount == 0) 50 * { 51 * // Timeout 52 * } 53 * else if (eventCount == -1) 54 * { 55 * // Another thread called the wakeup() method. 56 * } 57 * else 58 * { 59 * // Error: should never happen. 60 * } 61 * 62 * selector.close(); 63 * --- 64 */ 65 public class PollSelector: AbstractSelector 66 { 67 /** 68 * Alias for the select() method as we're not reimplementing it in 69 * this class. 70 */ 71 alias AbstractSelector.select select; 72 73 /** 74 * Default number of SelectionKey's that will be handled by the 75 * PollSelector. 76 */ 77 public enum uint DefaultSize = 64; 78 79 /** Map to associate the conduit handles with their selection keys */ 80 private PollSelectionKey[ISelectable.Handle] _keys; 81 //private SelectionKey[] _selectedKeys; 82 private pollfd[] _pfds; 83 private uint _count = 0; 84 private int _eventCount = 0; 85 86 /** 87 * Open the poll()-based selector. 88 * 89 * Params: 90 * size = maximum amount of conduits that will be registered; 91 * it will grow dynamically if needed. 92 * maxEvents = maximum amount of conduit events that will be 93 * returned in the selection set per call to select(); 94 * this value is currently not used by this selector. 95 */ 96 override public void open(uint size = DefaultSize, uint maxEvents = DefaultSize) 97 in 98 { 99 assert(size > 0); 100 } 101 body 102 { 103 _pfds = new pollfd[size]; 104 } 105 106 /** 107 * Close the selector. 108 * 109 * Remarks: 110 * It can be called multiple times without harmful side-effects. 111 */ 112 override public void close() 113 { 114 _keys = null; 115 //_selectedKeys = null; 116 _pfds = null; 117 _count = 0; 118 _eventCount = 0; 119 } 120 121 /** 122 * Associate a conduit to the selector and track specific I/O events. 123 * If a conduit is already associated, modify the events and 124 * attachment. 125 * 126 * Params: 127 * conduit = conduit that will be associated to the selector; 128 * must be a valid conduit (i.e. not null and open). 129 * events = bit mask of Event values that represent the events 130 * that will be tracked for the conduit. 131 * attachment = optional object with application-specific data that 132 * will be available when an event is triggered for the 133 * conduit 134 * 135 * Throws: 136 * RegisteredConduitException if the conduit had already been 137 * registered to the selector. 138 * 139 * Examples: 140 * --- 141 * selector.register(conduit, Event.Read | Event.Write, object); 142 * --- 143 */ 144 override public void register(ISelectable conduit, Event events, Object attachment = null) 145 in 146 { 147 assert(conduit !is null && conduit.fileHandle() >= 0); 148 } 149 body 150 { 151 debug (selector) 152 Stdout.formatln("--- PollSelector.register(handle={0}, events=0x{1:x})", 153 cast(int) conduit.fileHandle(), cast(uint) events); 154 155 PollSelectionKey* current = (conduit.fileHandle() in _keys); 156 157 if (current !is null) 158 { 159 debug (selector) 160 Stdout.formatln("--- Adding pollfd in index {0} (of {1})", 161 current.index, _count); 162 163 current.key.events = events; 164 current.key.attachment = attachment; 165 166 _pfds[current.index].events = cast(short) events; 167 } 168 else 169 { 170 if (_count == _pfds.length) 171 _pfds.length = _pfds.length + 1; 172 173 _pfds[_count].fd = conduit.fileHandle(); 174 _pfds[_count].events = cast(short) events; 175 _pfds[_count].revents = 0; 176 177 _keys[conduit.fileHandle()] = new PollSelectionKey(conduit, events, _count, attachment); 178 _count++; 179 } 180 } 181 182 /** 183 * Remove a conduit from the selector. 184 * 185 * Params: 186 * conduit = conduit that had been previously associated to the 187 * selector; it can be null. 188 * 189 * Remarks: 190 * Unregistering a null conduit is allowed and no exception is thrown 191 * if this happens. 192 * 193 * Throws: 194 * UnregisteredConduitException if the conduit had not been previously 195 * registered to the selector. 196 */ 197 override public void unregister(ISelectable conduit) 198 { 199 if (conduit !is null) 200 { 201 try 202 { 203 debug (selector) 204 Stdout.formatln("--- PollSelector.unregister(handle={0})", 205 cast(int) conduit.fileHandle()); 206 207 PollSelectionKey* removed = (conduit.fileHandle() in _keys); 208 209 if (removed !is null) 210 { 211 debug (selector) 212 Stdout.formatln("--- Removing pollfd in index {0} (of {1})", 213 removed.index, _count); 214 215 // 216 // instead of doing an O(n) remove, move the last 217 // element to the removed slot 218 // 219 _pfds[removed.index] = _pfds[_count - 1]; 220 _keys[cast(ISelectable.Handle)_pfds[removed.index].fd].index = removed.index; 221 _count--; 222 223 _keys.remove(conduit.fileHandle()); 224 } 225 else 226 { 227 debug (selector) 228 Stdout.formatln("--- PollSelector.unregister(handle={0}): conduit was not found", 229 cast(int) conduit.fileHandle()); 230 throw new UnregisteredConduitException(__FILE__, __LINE__); 231 } 232 } 233 catch (Exception e) 234 { 235 debug (selector) 236 Stdout.formatln("--- Exception inside PollSelector.unregister(handle={0}): {1}", 237 cast(int) conduit.fileHandle(), e.toString()); 238 239 throw new UnregisteredConduitException(__FILE__, __LINE__); 240 } 241 } 242 } 243 244 /** 245 * Wait for I/O events from the registered conduits for a specified 246 * amount of time. 247 * 248 * Params: 249 * timeout = Timespan with the maximum amount of time that the 250 * selector will wait for events from the conduits; the 251 * amount of time is relative to the current system time 252 * (i.e. just the number of milliseconds that the selector 253 * has to wait for the events). 254 * 255 * Returns: 256 * The amount of conduits that have received events; 0 if no conduits 257 * have received events within the specified timeout; and -1 if the 258 * wakeup() method has been called from another thread. 259 * 260 * Throws: 261 * InterruptedSystemCallException if the underlying system call was 262 * interrupted by a signal and the 'restartInterruptedSystemCall' 263 * property was set to false; SelectorException if there were no 264 * resources available to wait for events from the conduits. 265 */ 266 override public int select(TimeSpan timeout) 267 { 268 int to = (timeout != TimeSpan.max ? cast(int) timeout.millis : -1); 269 270 debug (selector) 271 Stdout.formatln("--- PollSelector.select({0} ms): waiting on {1} handles", 272 to, _count); 273 274 // We run the call to poll() inside a loop in case the system call 275 // was interrupted by a signal and we need to restart it. 276 while (true) 277 { 278 _eventCount = poll(_pfds.ptr, _count, to); 279 if (_eventCount < 0) 280 { 281 if (errno != EINTR || !_restartInterruptedSystemCall) 282 { 283 // The call to checkErrno() ends up throwing an exception 284 checkErrno(__FILE__, __LINE__); 285 } 286 debug (selector) 287 Stdout.print("--- Restarting poll() after being interrupted\n"); 288 } 289 else 290 { 291 // Timeout or got a selection. 292 break; 293 } 294 } 295 return _eventCount; 296 } 297 298 /** 299 * Return the selection set resulting from the call to any of the 300 * select() methods. 301 * 302 * Remarks: 303 * If the call to select() was unsuccessful or it did not return any 304 * events, the returned value will be null. 305 */ 306 override public ISelectionSet selectedSet() 307 { 308 return (_eventCount > 0 ? new PollSelectionSet(_pfds, _eventCount, _keys) : null); 309 } 310 311 /** 312 * Return the selection key resulting from the registration of a 313 * conduit to the selector. 314 * 315 * Remarks: 316 * If the conduit is not registered to the selector the returned 317 * value will SelectionKey.init. No exception will be thrown by this 318 * method. 319 */ 320 override public SelectionKey key(ISelectable conduit) 321 { 322 if(conduit !is null) 323 { 324 if(auto k = (conduit.fileHandle in _keys)) 325 { 326 return k.key; 327 } 328 } 329 return SelectionKey.init; 330 } 331 332 /** 333 * Return the number of keys resulting from the registration of a conduit 334 * to the selector. 335 */ 336 override public size_t count() 337 { 338 return _keys.length; 339 } 340 341 /** 342 * Iterate through the currently registered selection keys. Note that 343 * you should not erase or add any items from the selector while 344 * iterating, although you can register existing conduits again. 345 */ 346 public int opApply(scope int delegate(ref SelectionKey sk) dg) 347 { 348 int result = 0; 349 foreach(k; _keys) 350 { 351 SelectionKey sk = k.key; 352 if((result = dg(sk)) != 0) 353 break; 354 } 355 return result; 356 } 357 358 unittest 359 { 360 } 361 } 362 363 /** 364 * Class used to hold the list of Conduits that have received events. 365 */ 366 private class PollSelectionSet: ISelectionSet 367 { 368 pollfd[] fds; 369 int numSelected; 370 PollSelectionKey[ISelectable.Handle] keys; 371 372 373 this(pollfd[] fds, int numSelected, PollSelectionKey[ISelectable.Handle] keys) 374 { 375 this.fds = fds; 376 this.numSelected = numSelected; 377 this.keys = keys; 378 } 379 380 size_t length() 381 { 382 return numSelected; 383 } 384 385 /** 386 * Iterate over all the Conduits that have received events. 387 */ 388 int opApply(scope int delegate(ref SelectionKey) dg) 389 { 390 int rc = 0; 391 int nLeft = numSelected; 392 393 foreach (pfd; fds) 394 { 395 // 396 // see if the revent is set 397 // 398 if(pfd.revents != 0) 399 { 400 debug (selector) 401 Stdout.formatln("--- Found events 0x{0:x} for handle {1}", 402 cast(uint) pfd.revents, cast(int) pfd.fd); 403 auto k = (cast(ISelectable.Handle)pfd.fd) in keys; 404 if(k !is null) 405 { 406 SelectionKey current = k.key; 407 current.events = cast(Event)pfd.revents; 408 if ((rc = dg(current)) != 0) 409 { 410 break; 411 } 412 } 413 else 414 { 415 debug (selector) 416 Stdout.formatln("--- Handle {0} was not found in the Selector", 417 cast(int) pfd.fd); 418 } 419 if(--nLeft == 0) 420 break; 421 } 422 } 423 return rc; 424 } 425 } 426 427 /** 428 * Class that holds the information that the PollSelector needs to deal 429 * with each registered Conduit. 430 */ 431 private class PollSelectionKey 432 { 433 SelectionKey key; 434 uint index; 435 436 public this() 437 { 438 } 439 440 public this(ISelectable conduit, Event events, uint index, Object attachment) 441 { 442 this.key = SelectionKey(conduit, events, attachment); 443 444 this.index = index; 445 } 446 } 447 } 448