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 }