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