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.SelectSelector;
8 
9 
10 
11 public import tango.io.model.IConduit;
12 
13 private import Time = tango.core.Time;
14 public import tango.io.selector.model.ISelector;
15 
16 private import tango.io.selector.AbstractSelector;
17 private import tango.io.selector.SelectorException;
18 private import tango.sys.Common;
19 
20 private import tango.stdc.errno;
21 
22 debug (selector)
23 {
24     private import tango.io.Stdout;
25     private import tango.text.convert.Integer;
26 }
27 
28 
29 version (Windows)
30 {
31     private import tango.core.Thread;
32 
33     private
34     {
35         // Opaque struct
36         struct fd_set
37         {
38         }
39 
40         extern (Windows) int select(int nfds, fd_set* readfds, fd_set* writefds,
41                                     fd_set* errorfds, timeval* timeout);
42     }
43 }
44 
45 version (Posix)
46 {
47     private import tango.core.BitArray;
48 }
49 
50 
51 /**
52  * Selector that uses the select() system call to receive I/O events for
53  * the registered conduits. To use this class you would normally do
54  * something like this:
55  *
56  * Examples:
57  * ---
58  * import tango.io.selector.SelectSelector;
59  *
60  * Socket socket;
61  * ISelector selector = new SelectSelector();
62  *
63  * selector.open(100, 10);
64  *
65  * // Register to read from socket
66  * selector.register(socket, Event.Read);
67  *
68  * int eventCount = selector.select(0.1); // 0.1 seconds
69  * if (eventCount > 0)
70  * {
71  *     // We can now read from the socket
72  *     socket.read();
73  * }
74  * else if (eventCount == 0)
75  * {
76  *     // Timeout
77  * }
78  * else if (eventCount == -1)
79  * {
80  *     // Another thread called the wakeup() method.
81  * }
82  * else
83  * {
84  *     // Error: should never happen.
85  * }
86  *
87  * selector.close();
88  * ---
89  */
90 public class SelectSelector: AbstractSelector
91 {
92     /**
93      * Alias for the select() method as we're not reimplementing it in
94      * this class.
95      */
96     alias AbstractSelector.select select;
97 
98     uint _size;
99     private SelectionKey[ISelectable.Handle] _keys;
100     private HandleSet _readSet;
101     private HandleSet _writeSet;
102     private HandleSet _exceptionSet;
103     private HandleSet _selectedReadSet;
104     private HandleSet _selectedWriteSet;
105     private HandleSet _selectedExceptionSet;
106     int _eventCount;
107     version (Posix)
108     {
109         private ISelectable.Handle _maxfd = cast(ISelectable.Handle) -1;
110 
111         /**
112          * Default number of SelectionKey's that will be handled by the
113          * SelectSelector.
114          */
115         public enum uint DefaultSize = 1024;
116     }
117     else
118     {
119         /**
120          * Default number of SelectionKey's that will be handled by the
121          * SelectSelector.
122          */
123         public enum uint DefaultSize = 63;
124     }
125 
126     /**
127      * Open the select()-based selector.
128      *
129      * Params:
130      * size         = maximum amount of conduits that will be registered;
131      *                it will grow dynamically if needed.
132      * maxEvents    = maximum amount of conduit events that will be
133      *                returned in the selection set per call to select();
134      *                this value is currently not used by this selector.
135      */
136     override public void open(uint size = DefaultSize, uint maxEvents = DefaultSize)
137     in
138     {
139         assert(size > 0);
140     }
141     body
142     {
143         _size = size;
144     }
145 
146     /**
147      * Close the selector.
148      *
149      * Remarks:
150      * It can be called multiple times without harmful side-effects.
151      */
152     override public void close()
153     {
154         _size = 0;
155         _keys = null;
156         _readSet = HandleSet.init;
157         _writeSet = HandleSet.init;
158         _exceptionSet = HandleSet.init;
159         _selectedReadSet = HandleSet.init;
160         _selectedWriteSet = HandleSet.init;
161         _selectedExceptionSet = HandleSet.init;
162     }
163 
164     private HandleSet *allocateSet(ref HandleSet set, ref HandleSet selectedSet)
165     {
166         if(!set.initialized)
167         {
168             set.setup(_size);
169             selectedSet.setup(_size);
170         }
171         return &set;
172     }
173 
174     /**
175      * Associate a conduit to the selector and track specific I/O events.
176      * If a conduit is already associated with the selector, the events and
177      * attachment are upated.
178      *
179      * Params:
180      * conduit      = conduit that will be associated to the selector;
181      *                must be a valid conduit (i.e. not null and open).
182      * events       = bit mask of Event values that represent the events
183      *                that will be tracked for the conduit.
184      * attachment   = optional object with application-specific data that
185      *                will be available when an event is triggered for the
186      *                conduit
187      *
188      * Throws:
189      * RegisteredConduitException if the conduit had already been
190      * registered to the selector.
191      *
192      * Examples:
193      * ---
194      * selector.register(conduit, Event.Read | Event.Write, object);
195      * ---
196      */
197     override public void register(ISelectable conduit, Event events, Object attachment = null)
198     in
199     {
200         assert(conduit !is null && conduit.fileHandle());
201     }
202     body
203     {
204         ISelectable.Handle handle = conduit.fileHandle();
205 
206         debug (selector)
207             Stdout.format("--- SelectSelector.register(handle={0}, events=0x{1:x})\n",
208                           cast(int) handle, cast(uint) events);
209 
210         SelectionKey *key = (handle in _keys);
211         if (key !is null)
212         {
213             if ((events & Event.Read) || (events & Event.Hangup))
214             {
215                 allocateSet(_readSet, _selectedReadSet).set(handle);
216             }
217             else if (_readSet.initialized)
218             {
219                 _readSet.clear(handle);
220             }
221 
222             if ((events & Event.Write))
223             {
224                 allocateSet(_writeSet, _selectedWriteSet).set(handle);
225             }
226             else if (_writeSet.initialized)
227             {
228                 _writeSet.clear(handle);
229             }
230 
231             if (events & Event.Error)
232             {
233                 allocateSet(_exceptionSet, _selectedExceptionSet).set(handle);
234             }
235             else if (_exceptionSet.initialized)
236             {
237                 _exceptionSet.clear(handle);
238             }
239 
240             version (Posix)
241             {
242                 if (handle > _maxfd)
243                     _maxfd = handle;
244             }
245 
246             key.events = events;
247             key.attachment = attachment;
248         }
249         else
250         {
251             // Keep record of the Conduits for whom we're tracking events.
252             _keys[handle] = SelectionKey(conduit, events, attachment);
253 
254             if ((events & Event.Read) || (events & Event.Hangup))
255             {
256                 allocateSet(_readSet, _selectedReadSet).set(handle);
257             }
258 
259             if (events & Event.Write)
260             {
261                 allocateSet(_writeSet, _selectedWriteSet).set(handle);
262             }
263 
264             if (events & Event.Error)
265             {
266                 allocateSet(_exceptionSet, _selectedExceptionSet).set(handle);
267             }
268 
269             version (Posix)
270             {
271                 if (handle > _maxfd)
272                     _maxfd = handle;
273             }
274         }
275     }
276 
277     /**
278      * Remove a conduit from the selector.
279      *
280      * Params:
281      * conduit      = conduit that had been previously associated to the
282      *                selector; it can be null.
283      *
284      * Remarks:
285      * Unregistering a null conduit is allowed and no exception is thrown
286      * if this happens.
287      *
288      * Throws:
289      * UnregisteredConduitException if the conduit had not been previously
290      * registered to the selector.
291      */
292     override public void unregister(ISelectable conduit)
293     {
294         if (conduit !is null)
295         {
296             ISelectable.Handle handle = conduit.fileHandle();
297 
298             debug (selector)
299                 Stdout.format("--- SelectSelector.unregister(handle={0})\n",
300                               cast(int) handle);
301 
302             SelectionKey* removed = (handle in _keys);
303 
304             if (removed !is null)
305             {
306                 if (removed.events & Event.Error)
307                 {
308                     _exceptionSet.clear(handle);
309                 }
310                 if (removed.events & Event.Write)
311                 {
312                     _writeSet.clear(handle);
313                 }
314                 if ((removed.events & Event.Read) || (removed.events & Event.Hangup))
315                 {
316                     _readSet.clear(handle);
317                 }
318                 _keys.remove(handle);
319 
320                 version (Posix)
321                 {
322                     // If we're removing the biggest handle we've entered so far
323                     // we need to recalculate this value for the set.
324                     if (handle == _maxfd)
325                     {
326                         while (--_maxfd >= 0)
327                         {
328                             if (_readSet.isSet(_maxfd) ||
329                                 _writeSet.isSet(_maxfd) ||
330                                 _exceptionSet.isSet(_maxfd))
331                             {
332                                 break;
333                             }
334                         }
335                     }
336                 }
337             }
338             else
339             {
340                 debug (selector)
341                     Stdout.format("--- SelectSelector.unregister(handle={0}): conduit was not found\n",
342                                   cast(int) conduit.fileHandle());
343                 throw new UnregisteredConduitException(__FILE__, __LINE__);
344             }
345         }
346     }
347 
348     /**
349      * Wait for I/O events from the registered conduits for a specified
350      * amount of time.
351      *
352      * Params:
353      * timeout  = TimeSpan with the maximum amount of time that the
354      *            selector will wait for events from the conduits; the
355      *            amount of time is relative to the current system time
356      *            (i.e. just the number of milliseconds that the selector
357      *            has to wait for the events).
358      *
359      * Returns:
360      * The amount of conduits that have received events; 0 if no conduits
361      * have received events within the specified timeout; and -1 if the
362      * wakeup() method has been called from another thread.
363      *
364      * Throws:
365      * InterruptedSystemCallException if the underlying system call was
366      * interrupted by a signal and the 'restartInterruptedSystemCall'
367      * property was set to false; SelectorException if there were no
368      * resources available to wait for events from the conduits.
369      */
370     override public int select(TimeSpan timeout)
371     {
372         fd_set *readfds;
373         fd_set *writefds;
374         fd_set *exceptfds;
375         timeval tv;
376         version (Windows)
377             bool handlesAvailable = false;
378 
379         debug (selector)
380             Stdout.format("--- SelectSelector.select(timeout={0} msec)\n", timeout.millis);
381 
382         if (_readSet.initialized)
383         {
384             debug (selector)
385                 _readSet.dump("_readSet");
386 
387             version (Windows)
388                 handlesAvailable = handlesAvailable || (_readSet.length > 0);
389 
390             readfds = cast(fd_set*) _selectedReadSet.copy(_readSet);
391         }
392         if (_writeSet.initialized)
393         {
394             debug (selector)
395                 _writeSet.dump("_writeSet");
396 
397             version (Windows)
398                 handlesAvailable = handlesAvailable || (_writeSet.length > 0);
399 
400             writefds = cast(fd_set*) _selectedWriteSet.copy(_writeSet);
401         }
402         if (_exceptionSet.initialized)
403         {
404             debug (selector)
405                 _exceptionSet.dump("_exceptionSet");
406 
407             version (Windows)
408                 handlesAvailable = handlesAvailable || (_exceptionSet.length > 0);
409 
410             exceptfds = cast(fd_set*) _selectedExceptionSet.copy(_exceptionSet);
411         }
412 
413         version (Posix)
414         {
415             while (true)
416             {
417                 toTimeval(&tv, timeout);
418 
419                 // FIXME: add support for the wakeup() call.
420                 _eventCount = .select(_maxfd + 1, readfds, writefds, exceptfds, timeout is TimeSpan.max ? null : &tv);
421 
422                 debug (selector)
423                     Stdout.format("---   .select() returned {0} (maxfd={1})\n",
424                                   _eventCount, cast(int) _maxfd);
425                 if (_eventCount >= 0)
426                 {
427                     break;
428                 }
429                 else
430                 {
431                     if (errno != EINTR || !_restartInterruptedSystemCall)
432                     {
433                         // checkErrno() always throws an exception
434                         checkErrno(__FILE__, __LINE__);
435                     }
436                     debug (selector)
437                         Stdout.print("--- Restarting select() after being interrupted\n");
438                 }
439             }
440         }
441         else
442         {
443             // Windows returns an error when select() is called with all three
444             // handle sets empty, so we emulate the POSIX behavior by calling
445             // Thread.sleep().
446             if (handlesAvailable)
447             {
448                 toTimeval(&tv, timeout);
449 
450                 // FIXME: Can a system call be interrupted on Windows?
451                 _eventCount = .select(uint.max, readfds, writefds, exceptfds, timeout is TimeSpan.max ? null : &tv);
452 
453                 debug (selector)
454                     Stdout.format("---   .select() returned {0}\n", _eventCount);
455             }
456             else
457             {
458                 Thread.sleep(Time.seconds(timeout.interval()));
459                 _eventCount = 0;
460             }
461         }
462         return _eventCount;
463     }
464 
465     /**
466      * Return the selection set resulting from the call to any of the
467      * select() methods.
468      *
469      * Remarks:
470      * If the call to select() was unsuccessful or it did not return any
471      * events, the returned value will be null.
472      */
473     override public ISelectionSet selectedSet()
474     {
475         return (_eventCount > 0 ? new SelectSelectionSet(_keys, cast(uint) _eventCount, _selectedReadSet,
476                                                          _selectedWriteSet, _selectedExceptionSet) : null);
477     }
478 
479     /**
480      * Return the selection key resulting from the registration of a
481      * conduit to the selector.
482      *
483      * Remarks:
484      * If the conduit is not registered to the selector the returned
485      * value will be null. No exception will be thrown by this method.
486      */
487     override public SelectionKey key(ISelectable conduit)
488     {
489         if(conduit !is null)
490         {
491             if(auto k = conduit.fileHandle in _keys)
492             {
493                 return *k;
494             }
495         }
496         return SelectionKey.init;
497     }
498 
499     /**
500      * Return the number of keys resulting from the registration of a conduit
501      * to the selector.
502      */
503     override public size_t count()
504     {
505         return _keys.length;
506     }
507 
508     /**
509      * Iterate through the currently registered selection keys.  Note that
510      * you should not erase or add any items from the selector while
511      * iterating, although you can register existing conduits again.
512      */
513     int opApply(scope int delegate(ref SelectionKey) dg)
514     {
515         int result = 0;
516         foreach(v; _keys)
517         {
518             if((result = dg(v)) != 0)
519                 break;
520         }
521         return result;
522     }
523 }
524 
525 /**
526  * SelectionSet for the select()-based Selector.
527  */
528 private class SelectSelectionSet: ISelectionSet
529 {
530     SelectionKey[ISelectable.Handle] _keys;
531     uint _eventCount;
532     HandleSet _readSet;
533     HandleSet _writeSet;
534     HandleSet _exceptionSet;
535 
536     this(SelectionKey[ISelectable.Handle] keys, uint eventCount,
537                    HandleSet readSet, HandleSet writeSet, HandleSet exceptionSet)
538     {
539         _keys = keys;
540         _eventCount = eventCount;
541         _readSet = readSet;
542         _writeSet = writeSet;
543         _exceptionSet = exceptionSet;
544     }
545 
546     @property size_t length()
547     {
548         return _eventCount;
549     }
550 
551     int opApply(scope int delegate(ref SelectionKey) dg)
552     {
553         int rc = 0;
554         ISelectable.Handle handle;
555         Event events;
556 
557         debug (selector)
558             Stdout.format("--- SelectSelectionSet.opApply() ({0} elements)\n", _eventCount);
559 
560         foreach (SelectionKey current; _keys)
561         {
562             handle = current.conduit.fileHandle();
563 
564             if (_readSet.isSet(handle))
565                 events = Event.Read;
566             else
567                 events = Event.None;
568 
569             if (_writeSet.isSet(handle))
570                 events |= Event.Write;
571 
572             if (_exceptionSet.isSet(handle))
573                 events |= Event.Error;
574 
575             // Only invoke the delegate if there is an event for the conduit.
576             if (events != Event.None)
577             {
578                 current.events = events;
579 
580                 debug (selector)
581                     Stdout.format("---   Calling foreach delegate with selection key ({0}, 0x{1:x})\n",
582                                   cast(int) handle, cast(uint) events);
583 
584                 if ((rc = dg(current)) != 0)
585                 {
586                     break;
587                 }
588             }
589             else
590             {
591                 debug (selector)
592                     Stdout.format("---   Handle {0} doesn't have pending events\n",
593                                   cast(int) handle);
594             }
595         }
596         return rc;
597     }
598 }
599 
600 
601 version (Windows)
602 {
603     /**
604      * Helper class used by the select()-based Selector to store handles.
605      * On Windows the handles are kept in an array of uints and the first
606      * element of the array stores the array "length" (i.e. number of handles
607      * in the array). Everything is stored so that the native select() API
608      * can use the HandleSet without additional conversions by just casting it
609      * to a fd_set*.
610      */
611     private struct HandleSet
612     {
613         /** Default number of handles that will be held in the HandleSet. */
614         enum uint DefaultSize = 63;
615 
616         uint[] _buffer;
617 
618         /**
619          * Constructor. Sets the initial number of handles that will be held
620          * in the HandleSet.
621          */
622         void setup(uint size = DefaultSize)
623         {
624             _buffer = new uint[1 + size];
625             _buffer[0] = 0;
626         }
627 
628         /**
629          *  return true if this handle set has been initialized.
630          */
631         @property bool initialized()
632         {
633             return _buffer.length > 0;
634         }
635 
636         /**
637          * Return the number of handles present in the HandleSet.
638          */
639         @property uint length()
640         {
641             return _buffer[0];
642         }
643 
644         /**
645          * Add the handle to the set.
646          */
647         void set(ISelectable.Handle handle)
648         in
649         {
650             assert(handle);
651         }
652         body
653         {
654             if (!isSet(handle))
655             {
656                 // If we added too many sockets we increment the size of the buffer
657                 if (++_buffer[0] >= _buffer.length)
658                 {
659                     _buffer.length = _buffer[0] + 1;
660                 }
661                 _buffer[_buffer[0]] = cast(uint) handle;
662             }
663         }
664 
665         /**
666          * Remove the handle from the set.
667          */
668         void clear(ISelectable.Handle handle)
669         {
670             for (uint i = 1; i <= _buffer[0]; ++i)
671             {
672                 if (_buffer[i] == cast(uint) handle)
673                 {
674                     // We don't need to keep the handles in the order in which
675                     // they were inserted, so we optimize the removal by
676                     // copying the last element to the position of the removed
677                     // element.
678                     if (i != _buffer[0])
679                     {
680                         _buffer[i] = _buffer[_buffer[0]];
681                     }
682                     _buffer[0]--;
683                     return;
684                 }
685             }
686         }
687 
688         /**
689          * Copy the contents of the HandleSet into this instance.
690          */
691         HandleSet copy(HandleSet handleSet)
692         {
693             if(handleSet._buffer.length > _buffer.length)
694             {
695                 _buffer.length = handleSet._buffer[0] + 1;
696             }
697 
698 
699             _buffer[] = handleSet._buffer[0.._buffer.length];
700             return this;
701         }
702 
703         /**
704          * Check whether the handle has been set.
705          */
706         public bool isSet(ISelectable.Handle handle)
707         {
708             if(_buffer.length == 0)
709                 return false;
710 
711             uint* start;
712             uint* stop;
713 
714             for (start = _buffer.ptr + 1, stop = start + _buffer[0]; start != stop; start++)
715             {
716                 if (*start == cast(uint) handle)
717                     return true;
718             }
719             return false;
720         }
721 
722         /**
723          * Cast the current object to a pointer to an fd_set, to be used with the
724          * select() system call.
725          */
726         public fd_set* opCast()
727         {
728             return cast(fd_set*) _buffer.ptr;
729         }
730 
731 
732         debug (selector)
733         {
734             /**
735              * Dump the contents of a HandleSet into stdout.
736              */
737             void dump(const(char)[] name = null)
738             {
739                 if (_buffer !is null && _buffer.length > 0 && _buffer[0] > 0)
740                 {
741                     const(char)[] handleStr = new char[16];
742                     const(char)[] handleListStr;
743                     bool isFirst = true;
744 
745                     if (name is null)
746                     {
747                         name = "HandleSet";
748                     }
749 
750                     for (uint i = 1; i < _buffer[0]; ++i)
751                     {
752                         if (!isFirst)
753                         {
754                             handleListStr ~= ", ";
755                         }
756                         else
757                         {
758                             isFirst = false;
759                         }
760 
761                         handleListStr ~= itoa(handleStr, _buffer[i]);
762                     }
763 
764                     Stdout.formatln("--- {0}[{1}]: {2}", name, _buffer[0], handleListStr);
765                 }
766             }
767         }
768     }
769 }
770 else version (Posix)
771 {
772     private import tango.core.BitManip;
773 
774     /**
775      * Helper class used by the select()-based Selector to store handles.
776      * On POSIX-compatible platforms the handles are kept in an array of bits.
777      * Everything is stored so that the native select() API can use the
778      * HandleSet without additional conversions by casting it to a fd_set*.
779      */
780     private struct HandleSet
781     {
782         /** Default number of handles that will be held in the HandleSet. */
783         enum uint DefaultSize     = 1024;
784 
785         BitArray _buffer;
786 
787         /**
788          * Constructor. Sets the initial number of handles that will be held
789          * in the HandleSet.
790          */
791         void setup(uint size = DefaultSize)
792         {
793             if (size < 1024)
794                 size = 1024;
795 
796             _buffer.length = size;
797         }
798 
799         /**
800          * Return true if the handleset has been initialized
801          */
802         @property bool initialized()
803         {
804             return _buffer.length > 0;
805         }
806 
807         /**
808          * Add a handle to the set.
809          */
810         public void set(ISelectable.Handle handle)
811         {
812             // If we added too many sockets we increment the size of the buffer
813             uint fd = cast(uint)handle;
814             if(fd >= _buffer.length)
815                 _buffer.length = fd + 1;
816             _buffer[fd] = true;
817         }
818 
819         /**
820          * Remove a handle from the set.
821          */
822         public void clear(ISelectable.Handle handle)
823         {
824             auto fd = cast(uint)handle;
825             if(fd < _buffer.length)
826                 _buffer[fd] = false;
827         }
828 
829         /**
830          * Copy the contents of the HandleSet into this instance.
831          */
832         HandleSet copy(HandleSet handleSet)
833         {
834             //
835             // adjust the length if necessary
836             //
837             if(handleSet._buffer.length != _buffer.length)
838                 _buffer.length = handleSet._buffer.length;
839             
840             _buffer[] = handleSet._buffer;
841             return this;
842         }
843 
844         /**
845          * Check whether the handle has been set.
846          */
847         bool isSet(ISelectable.Handle handle)
848         {
849             auto fd = cast(uint)handle;
850             if(fd < _buffer.length)
851                 return _buffer[fd];
852             return false;
853         }
854 
855         /**
856          * Cast the current object to a pointer to an fd_set, to be used with the
857          * select() system call.
858          */
859         fd_set* opCast()
860         {
861             return cast(fd_set*) _buffer.ptr;
862         }
863 
864         debug (selector)
865         {
866             /**
867              * Dump the contents of a HandleSet into stdout.
868              */
869             void dump(const(char)[] name = null)
870             {
871                 if (_buffer !is null && _buffer.length > 0)
872                 {
873                     const(char)[] handleStr = new char[16];
874                     const(char)[] handleListStr;
875                     bool isFirst = true;
876 
877                     if (name is null)
878                     {
879                         name = "HandleSet";
880                     }
881 
882                     for (uint i = 0; i < _buffer.length * _buffer[0].sizeof; ++i)
883                     {
884                         if (isSet(cast(ISelectable.Handle) i))
885                         {
886                             if (!isFirst)
887                             {
888                                 handleListStr ~= ", ";
889                             }
890                             else
891                             {
892                                 isFirst = false;
893                             }
894                             handleListStr ~= itoa(handleStr, i);
895                         }
896                     }
897                     Stdout.formatln("--- {0}: {1}", name, handleListStr);
898                 }
899             }
900         }
901     }
902 }