1 /******************************************************************************* 2 3 Manages an I/O event loop with automatic handler invocation and 4 unregistration. 5 6 The EpollSelectDispatcher class wraps a Tango EpollSelector and uses 7 ISelectClient instances for Select I/O event registration, unregistration 8 and event handler invocation. An I/O event loop is provided that runs while 9 there are select event registrations. This loop automatically invokes the 10 registered handlers; via the return value each handler may indicate that it 11 wishes to be unregistered. After the ISelectClient instance has been 12 unregistered, its finalize() method is invoked. 13 14 If a handler throws an Exception, it is caught, the ISelectClient containing 15 that handler is unregistered immediately and finalize() is invoked. 16 Exceptions thrown by the ISelectClient's finalize() methods are also caught. 17 18 Copyright: 19 Copyright (c) 2009-2016 dunnhumby Germany GmbH. 20 All rights reserved. 21 22 License: 23 Boost Software License Version 1.0. See LICENSE_BOOST.txt for details. 24 Alternatively, this file may be distributed under the terms of the Tango 25 3-Clause BSD License (see LICENSE_BSD.txt for details). 26 27 *******************************************************************************/ 28 29 module ocean.io.select.EpollSelectDispatcher; 30 31 import core.stdc.stdlib: bsearch, qsort; 32 import core.stdc.errno: errno, EINTR, ENOENT, EEXIST, ENOMEM, EINVAL; 33 34 import ocean.core.Array : copy; 35 import ocean.core.array.Search; 36 import ocean.core.Verify; 37 import ocean.io.select.client.model.ISelectClient; 38 import ocean.io.select.client.SelectEvent; 39 import ocean.io.select.selector.EpollException; 40 import ocean.io.select.selector.IEpollSelectDispatcherInfo; 41 import ocean.io.select.selector.model.ISelectedKeysHandler; 42 import ocean.io.select.selector.RegisteredClients; 43 import ocean.io.select.selector.SelectedKeysHandler; 44 import ocean.io.select.selector.TimeoutSelectedKeysHandler; 45 import ocean.meta.types.Qualifiers; 46 import ocean.sys.Epoll; 47 import ocean.time.timeout.model.ITimeoutManager; 48 import ocean.util.container.AppendBuffer; 49 import ocean.util.container.queue.DynamicQueue; 50 import ocean.util.ReusableException; 51 52 debug (ISelectClient) import ocean.io.Stdout; 53 54 version (unittest) 55 { 56 debug = EpollFdSanity; 57 } 58 59 debug (EpollFdSanity) 60 { 61 import ocean.io.select.selector.EpollFdSanity; 62 } 63 64 /******************************************************************************* 65 66 EpollSelectDispatcher 67 68 *******************************************************************************/ 69 70 public class EpollSelectDispatcher : IEpollSelectDispatcherInfo 71 { 72 /*************************************************************************** 73 74 Event alias used internally 75 76 **************************************************************************/ 77 78 alias ISelectClient.Event Event; 79 80 /*************************************************************************** 81 82 Delegate to be called each time select cycle finished. 83 84 **************************************************************************/ 85 86 alias void delegate () SelectCycleCallback; 87 88 /*************************************************************************** 89 90 Set of registered clients. 91 92 **************************************************************************/ 93 94 private IRegisteredClients registered_clients; 95 96 /************************************************************************** 97 98 Default maximum number of file descriptors for which events can be 99 reported with one epoll_wait() call. 100 101 **************************************************************************/ 102 103 public static immutable uint DefaultMaxEvents = 16; 104 105 /************************************************************************** 106 107 true if the timeout feature is enabled. 108 109 **************************************************************************/ 110 111 public bool timeout_enabled ( ) 112 { 113 return this._timeout_enabled; 114 } 115 116 private bool _timeout_enabled; 117 118 /*************************************************************************** 119 120 Wrapped Epoll file handle 121 122 **************************************************************************/ 123 124 private Epoll epoll; 125 126 /*************************************************************************** 127 128 Reused list of events. 129 130 **************************************************************************/ 131 132 private epoll_event_t[] events; 133 134 /*************************************************************************** 135 136 List of the clients that we're currently handling. Always slices 137 this.events. 138 139 ***************************************************************************/ 140 141 private epoll_event_t[] selected_set; 142 143 /*************************************************************************** 144 145 Queue of delegates to be called (and dismissed) after the 146 epoll select cycle finishes. 147 148 ***************************************************************************/ 149 150 private DynamicQueue!(SelectCycleCallback) select_cycle_callbacks; 151 152 /*************************************************************************** 153 154 Re-usable errno exception 155 156 **************************************************************************/ 157 158 private EpollException e; 159 160 /*************************************************************************** 161 162 Optional hook to be called on unhandled exceptions from event callbacks. 163 164 NB: it won't be called on actual event errors thrown as EpollException 165 as those are expected to be handled on select client level exclusively. 166 167 ***************************************************************************/ 168 169 private bool delegate (Exception) unhandled_exception_hook; 170 171 /*************************************************************************** 172 173 Timeout manager instance; null disables the timeout feature. 174 175 **************************************************************************/ 176 177 private ITimeoutManager timeout_manager; 178 179 /*************************************************************************** 180 181 Event which is triggered when the shutdown() method is called. 182 183 **************************************************************************/ 184 185 private SelectEvent shutdown_event; 186 187 /*************************************************************************** 188 189 Flag which the eventLoop checks for exit status. Set to true when the 190 shutdown event fires (via calling the shutdown() method). 191 192 **************************************************************************/ 193 194 private bool shutdown_triggered; 195 196 /*************************************************************************** 197 198 Flag set to true when the eventLoop() method is called, and to false 199 when it exits. Used to assert that the event loop is not started from 200 within itself. 201 202 **************************************************************************/ 203 204 private bool in_event_loop; 205 206 version ( EpollCounters ) 207 { 208 /*********************************************************************** 209 210 Struct containing counters to track stats about the selector. 211 212 ***********************************************************************/ 213 214 private struct Counters 215 { 216 ulong selects; 217 ulong timeouts; 218 } 219 220 private Counters counters; 221 } 222 223 /*************************************************************************** 224 225 Client handler. 226 227 **************************************************************************/ 228 229 private ISelectedKeysHandler handle; 230 231 /*************************************************************************** 232 233 Constructor 234 235 Params: 236 timeout_manager = timeout manager instance (null disables the 237 timeout feature) 238 max_events = sets the maximum number of events that will be 239 returned in the selection set per call to select. 240 241 Throws: 242 EpollException on error obtaining a new epoll instance. 243 244 **************************************************************************/ 245 246 public this ( ITimeoutManager timeout_manager = null, uint max_events = DefaultMaxEvents ) 247 { 248 debug ( ISelectClient ) 249 { 250 this.registered_clients = new ClientSet; 251 } 252 else 253 { 254 this.registered_clients = new ClientCount; 255 } 256 257 this.e = new EpollException; 258 259 this.e.enforce( 260 this.epoll.create() >= 0, 261 "error creating epoll object", 262 "epoll_create" 263 ); 264 265 this.timeout_manager = timeout_manager; 266 267 this._timeout_enabled = timeout_manager !is null; 268 269 this.shutdown_event = new SelectEvent(&this.shutdownTrigger); 270 271 this.events = new epoll_event_t[max_events]; 272 273 this.handle = this.timeout_enabled? 274 new TimeoutSelectedKeysHandler(&this.unregister, this.e, this.timeout_manager, max_events) : 275 new SelectedKeysHandler(&this.unregister, this.e); 276 277 this.select_cycle_callbacks = new DynamicQueue!(SelectCycleCallback); 278 this.select_cycle_callbacks.auto_shrink = false; 279 } 280 281 /*************************************************************************** 282 283 Constructor; disables the timeout feature. 284 285 Params: 286 max_events = sets the maximum number of events that will be 287 returned in the selection set per call to select. 288 289 **************************************************************************/ 290 291 public this ( uint max_events ) 292 { 293 this(null, max_events); 294 } 295 296 /*************************************************************************** 297 298 Destructor. 299 300 **************************************************************************/ 301 302 ~this ( ) 303 { 304 with (this.epoll) if (fd >= 0) 305 { 306 close(); 307 } 308 } 309 310 /*************************************************************************** 311 312 Adds new callback to the queue of delegates to be called after the 313 current select cycle. It will only be kept for a single cycle. 314 315 Params: 316 cb = callback to call once after the current cycle ends. Must return 317 `true` to block event loop from terminating in the current 318 cycle. Returning `false` is primarily intended for internal 319 epoll/scheduler facilities and unlikely to be of use in an 320 application code. 321 322 ***************************************************************************/ 323 324 public void onCycleEnd ( scope SelectCycleCallback cb ) 325 { 326 this.select_cycle_callbacks.push(cb); 327 } 328 329 /*************************************************************************** 330 331 Adds or modifies a client registration. 332 333 To change the client of a currently registered conduit when several 334 clients share the same conduit, use changeClient(). 335 336 Important note: client is stored in a memory location not managed by the 337 D runtime memory manager (aka Garbage Collector). Therefore it is 338 important that the caller makes sure client is stored somewhere visible 339 to the GC (in a class variable, for example) so it won't get garbage 340 collected and deleted. 341 342 Params: 343 client = client to register, please make sure it is stored somewhere 344 visible to the garbage collector 345 346 Returns: 347 true if everything worked as expected or false if the client was 348 unexpectedly unregistered as it happens when its file descriptor is 349 closed. 350 351 Throws: 352 EpollException on error. 353 354 **************************************************************************/ 355 356 public bool register ( ISelectClient client ) 357 { 358 try if (client.is_registered) 359 { 360 scope (failure) 361 { 362 this.registered_clients -= client; 363 } 364 365 return this.modify(client); 366 } 367 else 368 { 369 this.e.enforce( 370 this.epollCtl(epoll.CtlOp.EPOLL_CTL_ADD, client.fileHandle, 371 client.events, client) == 0, 372 "error adding epoll registration", 373 "epoll_ctl" 374 ); 375 376 this.registered_clients += client; 377 378 return true; 379 } 380 catch (Exception e) 381 { 382 debug ( ISelectClient ) 383 { 384 Stderr.formatln("{} :: Error during register: '{}' @{}:{}", 385 client, e.message(), e.file, e.line).flush(); 386 } 387 throw e; 388 } 389 } 390 391 /*************************************************************************** 392 393 Removes a client registration. Does not fail/throw if the client is not 394 registered. 395 396 Params: 397 client = client to unregister 398 remove_from_selected_set = if true, removes the client from the 399 selected set that may be currently being iterated over. This 400 guarantees that the unregistered client's handle method will not 401 be subsequently called by the selector. The client may thus be 402 safely destroyed after unregistering. 403 404 Returns: 405 0 if everything worked as expected or the error code (errno) as a 406 warning on minor errors, that is, everything except ENOMEM (out of 407 memory) and EINVAL (invalid epoll file descriptor or epoll_ctl() 408 opcode). 409 ENOENT is a minor error that happens regularly when the client was 410 unexpectedly unregistered as it happens when its file descriptor is 411 closed. 412 413 Throws: 414 EpollException on the fatal errors ENOMEM and EINVAL. 415 416 **************************************************************************/ 417 418 public int unregister ( ISelectClient client, 419 bool remove_from_selected_set = false ) 420 { 421 try if (client.is_registered) 422 { 423 scope (success) 424 { 425 this.registered_clients -= client; 426 427 if (remove_from_selected_set) 428 { 429 /// Predicate for finding the ISelectClient inside 430 /// array of epoll_event_t entries. 431 scope entry_to_client = (const(epoll_event_t) entry) { 432 return entry.data.ptr == cast(void*)client; 433 }; 434 435 auto index = this.selected_set.findIf(entry_to_client); 436 // Instead of removing the array entry, we'll just invalidate 437 // it. This is to avoid problems with both the fact that 438 // SelectedKeysHandler might be foreach-iterating over 439 // this array at this time and the fact that shrinking 440 // the slice owned by EpollSelectDispatcher wouldn't shrink 441 // the slices owned by SelectedKeysHandler. 442 if (index < this.selected_set.length) 443 { 444 this.selected_set[index] = epoll_event_t.init; 445 } 446 } 447 } 448 449 if (!this.epollCtl(epoll.CtlOp.EPOLL_CTL_DEL, client.fileHandle, 450 client.events, client)) 451 { 452 return 0; 453 } 454 else 455 { 456 int errnum = .errno; 457 458 switch (errnum) 459 { 460 default: 461 return errnum; 462 463 case ENOMEM, EINVAL: 464 throw this.e.set(errnum) 465 .addMessage("error removing epoll client"); 466 } 467 } 468 } 469 else 470 { 471 return false; 472 } 473 catch (Exception e) 474 { 475 debug ( ISelectClient ) 476 { 477 Stderr.formatln("{} :: Error during unregister: '{}' @{}:{}", 478 client, e.message(), e.file, e.line).flush(); 479 } 480 throw e; 481 } 482 } 483 484 /************************************************************************** 485 486 Changes the clients of a registered conduit from current to next. 487 488 - current and next are expected to share the the same file descriptor 489 (conduit file handle), 490 - current is expected to be registered while next is expected not to be 491 registered. It is tolerated if current is unexpectedly unregistered 492 as it happens when its file descriptor is closed. 493 494 Important note: next is stored in a memory location not managed by the 495 D runtime memory manager (aka Garbage Collector). Therefore it is 496 important that the caller makes sure next is stored somewhere visible 497 to the GC (in a class variable, for example) so it won't get garbage 498 collected and deleted. 499 500 Params: 501 current = currently registered client to be unregistered 502 next = currently unregistered client to be registered, please 503 make sure it is stored somewhere visible to the garbage 504 collector 505 506 Returns: 507 true if everything worked as expected or false if current is 508 unexpectedly unregistered as it happens when its file descriptor is 509 closed. 510 511 Throws: 512 EpollException on error. 513 514 In: 515 - current and next must have the the same file descriptor, 516 - current.is_registered must be true, 517 - next.is_registered must be false. 518 519 **************************************************************************/ 520 521 public bool changeClient ( ISelectClient current, ISelectClient next ) 522 { 523 debug ( ISelectClient ) 524 { 525 if (current.fileHandle != next.fileHandle) 526 { 527 Stderr.formatln("Error during changeClient: current.fileHandle != next.fileHandle").flush(); 528 } 529 530 if (!current.is_registered) 531 { 532 Stderr.formatln("Error during changeClient: !current.is_registered").flush(); 533 } 534 535 if (next.is_registered) 536 { 537 Stderr.formatln("Error during changeClient: next.is_registered").flush(); 538 } 539 } 540 541 verify(current.fileHandle == next.fileHandle, 542 typeof (this).stringof ~ ".changeClient: clients are expected to share the same file descriptor"); 543 verify(current.is_registered, 544 typeof (this).stringof ~ ".changeClient: current client is expected to be registered"); 545 verify(!next.is_registered, 546 typeof (this).stringof ~ ".changeClient: next client is expected not to be registered"); 547 verify(current !is next); // should be impossible since current.is_registered != next.is_registered 548 549 try 550 { 551 scope (success) 552 { 553 debug ( ISelectClient ) 554 { 555 Stderr.formatln("Changed clients for fd:").flush(); 556 Stderr.formatln(" Replaced {}", current).flush(); 557 Stderr.formatln(" with {}", next).flush(); 558 } 559 560 this.registered_clients -= current; 561 this.registered_clients += next; 562 } 563 564 return this.modify(next); 565 } 566 catch (Exception e) 567 { 568 debug ( ISelectClient ) 569 { 570 Stderr.formatln("Error during changeClient: '{}' @{}:{}", 571 e.message(), e.file, e.line).flush(); 572 } 573 throw e; 574 } 575 } 576 577 /************************************************************************** 578 579 IEpollSelectDispatcherInfo interface method. 580 581 Returns: 582 the number of clients registered with the select dispatcher 583 584 **************************************************************************/ 585 586 public size_t num_registered ( ) 587 { 588 return this.registered_clients.length; 589 } 590 591 version ( EpollCounters ) 592 { 593 /*********************************************************************** 594 595 Returns: 596 the number of select calls (epoll_wait()) since the instance was 597 created (or since the ulong counter wrapped) 598 599 ***********************************************************************/ 600 601 public ulong selects ( ) 602 { 603 return this.counters.selects; 604 } 605 606 607 /*********************************************************************** 608 609 Returns: 610 the number of select calls (epoll_wait()) which exited due to a 611 timeout (as opposed to a client firing) since the instance was 612 created (or since the ulong counter wrapped) 613 614 ***********************************************************************/ 615 616 public ulong timeouts ( ) 617 { 618 return this.counters.timeouts; 619 } 620 621 622 /*********************************************************************** 623 624 Resets the counters returned by selects() and timeouts(). 625 626 ***********************************************************************/ 627 628 public void resetCounters ( ) 629 { 630 this.counters = this.counters.init; 631 } 632 } 633 634 /************************************************************************** 635 636 Modifies the registration of client using EPOLL_CTL_MOD. 637 More precisely, the events of the current registration of 638 client.fileHandle are set to client.events and the registration 639 attachment is set to client. 640 641 If this fails with ENOENT, which means, client.fileHandle turned 642 out not to be registered, a new registration of the client is created 643 using EPOLL_CTL_ADD. This fallback is intended only to be used when a 644 file descriptor is unexpectedly unregistered as it happens when it is 645 closed. 646 647 Params: 648 client = client to set the conduit registration to 649 650 Returns: 651 true if everything worked as expected or false if 652 client.fileHandle turned out not to be registered so that 653 a new registration was added. 654 655 Throws: 656 EpollException on error. 657 658 **************************************************************************/ 659 660 public bool modify ( ISelectClient client ) 661 { 662 if (!this.epollCtl(epoll.CtlOp.EPOLL_CTL_MOD, client.fileHandle, 663 client.events, client)) 664 { 665 return false; 666 } 667 else 668 { 669 int errnum = .errno; 670 671 if (errnum == ENOENT) 672 { 673 if (!this.epollCtl(epoll.CtlOp.EPOLL_CTL_ADD, client.fileHandle, 674 client.events, client)) 675 { 676 return true; 677 } 678 else 679 { 680 throw this.e.useGlobalErrno().addMessage( 681 "error adding epoll registration " 682 ~ "after modification resulted in ENOENT" 683 ); 684 } 685 } 686 else 687 { 688 throw this.e.useGlobalErrno().addMessage( 689 "error modifying epoll registration"); 690 } 691 } 692 } 693 694 /************************************************************************** 695 696 Sets a timeout manager expiry registration to client if the timeout 697 feature is enabled. This must be done exactly once for each select 698 client that should be able to time out. 699 If the timeout feature is disabled, nothing is done. 700 701 Params: 702 client = client to set timeout manager expiry registration 703 704 Returns: 705 true on success or false if the timeout feature is disabled. 706 707 **************************************************************************/ 708 709 public bool setExpiryRegistration ( ISelectClient client ) 710 { 711 if (this._timeout_enabled) 712 { 713 client.expiry_registration = this.timeout_manager.getRegistration(client); 714 return true; 715 } 716 else 717 { 718 return false; 719 } 720 } 721 722 /*************************************************************************** 723 724 Causes the event loop to exit before entering the next wait cycle. 725 726 Note that after calling shutdown() the select dispatcher is left in an 727 invalid state, where register() and unregister() calls will cause seg 728 faults and further calls to eventLoop() will exit immediately. 729 (See TODO in eventLoop().) 730 731 **************************************************************************/ 732 733 public void shutdown ( ) 734 { 735 this.register(this.shutdown_event); 736 this.shutdown_event.trigger(); 737 } 738 739 /*************************************************************************** 740 741 While there are clients registered, repeatedly waits for registered 742 events to happen, invokes the corresponding event handlers of the 743 registered clients and unregisters the clients if they desire so. 744 745 Params: 746 select_cycle_hook = if not null, will be called each time select 747 cycle finished before waiting for more events. Also called 748 once before the first select. If returns `true`, epoll will 749 return immediately if there are no active events. 750 unhandled_exception_hook = if not null, will be called each time 751 select cycle results in unhandled exception. May either rethrow 752 or consume (ignore) exception instance after processing it. 753 754 **************************************************************************/ 755 756 public void eventLoop ( scope bool delegate ( ) select_cycle_hook = null, 757 scope bool delegate (Exception) unhandled_exception_hook = null ) 758 { 759 verify(!this.in_event_loop, "Event loop has already been started."); 760 761 this.in_event_loop = true; 762 scope ( exit ) this.in_event_loop = false; 763 764 this.unhandled_exception_hook = unhandled_exception_hook; 765 766 bool caller_work_pending = false; 767 768 if (select_cycle_hook !is null) 769 caller_work_pending = select_cycle_hook(); 770 771 while ( (this.registered_clients.length || 772 this.select_cycle_callbacks.length) && 773 !this.shutdown_triggered ) 774 { 775 caller_work_pending = false; 776 777 try 778 { 779 this.select(caller_work_pending || 780 this.select_cycle_callbacks.length > 0); 781 } 782 catch (Exception e) 783 { 784 if ( (unhandled_exception_hook is null) 785 || !unhandled_exception_hook(e)) 786 { 787 throw e; 788 } 789 } 790 791 if (select_cycle_hook !is null) 792 caller_work_pending = select_cycle_hook(); 793 794 auto count = this.select_cycle_callbacks.length(); 795 796 for (int i; i < count; i++) 797 { 798 auto pcb = this.select_cycle_callbacks.pop(); 799 (*pcb)(); 800 } 801 802 this.select_cycle_callbacks.shrink(); 803 } 804 } 805 806 /*************************************************************************** 807 808 Executes an epoll select. 809 810 Params: 811 exit_asap = if set to 'true', epoll will exit immediately 812 if there are no events to trigger. Otherwise epoll will wait 813 indefinitely until any event fires. 814 815 Returns: 816 the number of epoll keys for which an event was reported. 817 818 **************************************************************************/ 819 820 protected uint select ( bool exit_asap ) 821 { 822 debug ( ISelectClient ) 823 { 824 Stderr.formatln("{}.select ({} clients registered):", 825 typeof(this).stringof, this.registered_clients.length).flush(); 826 size_t i; 827 foreach ( client; cast(ClientSet)this.registered_clients ) 828 { 829 Stderr.formatln(" {,3}: {}", i++, client).flush(); 830 } 831 } 832 833 while (true /* actually while epoll_wait is interrupted by a signal */) 834 { 835 ulong us_left = (this.timeout_manager !is null) 836 ? timeout_manager.us_left 837 : ulong.max; 838 839 // Note that timeout_manager.us_left can be ulong.max, too. 840 841 bool have_timeout = us_left < us_left.max; 842 843 // have_timeout is true if a timeout is specified, no matter if 844 // epoll_wait actually timed out or not (this is indicated by 845 // n == 0). 846 847 int epoll_wait_time; 848 849 if (exit_asap) 850 epoll_wait_time = 0; 851 else 852 { 853 if (have_timeout) 854 epoll_wait_time = cast (int) this.usToMs(us_left); 855 else 856 epoll_wait_time = -1; 857 } 858 859 int n = this.epoll.wait(this.events, epoll_wait_time); 860 861 version ( EpollCounters ) this.counters.selects++; 862 863 if (n >= 0) 864 { 865 debug ( ISelectClient ) if ( !n ) 866 { 867 Stderr.formatln("{}.select: timed out after {}microsec", 868 typeof(this).stringof, us_left).flush(); 869 } 870 871 version ( EpollCounters ) if ( n == 0 ) this.counters.timeouts++; 872 873 this.selected_set = this.events[0 .. n]; 874 scope (exit) 875 this.selected_set = null; 876 877 this.handle(this.selected_set, this.unhandled_exception_hook); 878 879 return n; 880 } 881 else 882 { 883 int errnum = .errno; 884 885 if (errnum != EINTR) 886 { 887 throw this.e.useGlobalErrno().addMessage( 888 "error waiting for epoll events"); 889 } 890 } 891 } 892 } 893 894 /*************************************************************************** 895 896 Creates/deletes/modifies registration inside the epoll. Wrapped to 897 allow for the sanity checks on the EpollSelectDispatcher's level. 898 899 Params: 900 op = epoll_ctl opcode 901 fd = file descriptor to register for events 902 events = events to register fd for 903 client = user ISelectClient to set data.obj of the created epoll_data_t 904 instance to 905 906 Returns: 907 0 on success or -1 on error. On error errno is set appropriately. 908 909 ***************************************************************************/ 910 911 private int epollCtl ( Epoll.CtlOp op, int fd, Epoll.Event events, 912 ISelectClient client ) 913 { 914 debug (EpollFdSanity) 915 { 916 return this.epoll.ctl(op, fd, client.events, 917 FdObjEpollData.encode(client, client.fileHandle)); 918 } 919 else 920 { 921 return this.epoll.ctl(op, fd, client.events, client); 922 } 923 } 924 925 /*************************************************************************** 926 927 Called when the shutdown event fires (via a call to the shutdown() 928 method). Sets the shutdown flag, ensuring that the event loop will exit, 929 regardless of whether there are any clients still registered. 930 931 Returns: 932 true to stay registered in the selector 933 934 **************************************************************************/ 935 936 private bool shutdownTrigger ( ) 937 { 938 this.shutdown_triggered = true; 939 940 return true; 941 } 942 943 /*************************************************************************** 944 945 Converts a microseconds value to milliseconds for use in select(). 946 It is crucial that this conversion always rounds up. Otherwise the 947 timeout manager might not find a timed out client after select() has 948 reported a timeout. 949 950 Params: 951 us = time value in microseconds 952 953 Returns: 954 nearest time value in milliseconds that is not less than us. 955 956 **************************************************************************/ 957 958 private static ulong usToMs ( ulong us ) 959 { 960 ulong ms = us / 1000; 961 962 return ms + ((us - ms * 1000) != 0); 963 } 964 }