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