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.AbstractSelector;
8 
9 
10 public import tango.io.model.IConduit;
11 public import tango.io.selector.SelectorException;
12 
13 private import tango.io.selector.model.ISelector;
14 private import tango.sys.Common;
15 private import tango.stdc.errno;
16 
17 version (Windows)
18 {
19     public struct timeval
20     {
21         int tv_sec;     // seconds
22         int tv_usec;    // microseconds
23     }
24 }
25 
26 /**
27  * Base class for all selectors.
28  *
29  * A selector is a multiplexor for I/O events associated to a Conduit.
30  * All selectors must implement this interface.
31  *
32  * A selector needs to be initialized by calling the open() method to pass
33  * it the initial amount of conduits that it will handle and the maximum
34  * amount of events that will be returned per call to select(). In both cases,
35  * these values are only hints and may not even be used by the specific
36  * ISelector implementation you choose to use, so you cannot make any
37  * assumptions regarding what results from the call to select() (i.e. you
38  * may receive more or less events per call to select() than what was passed
39  * in the 'maxEvents' argument. The amount of conduits that the selector can
40  * manage will be incremented dynamically if necessary.
41  *
42  * To add or modify conduit registrations in the selector, use the register()
43  * method.  To remove conduit registrations from the selector, use the
44  * unregister() method.
45  *
46  * To wait for events from the conduits you need to call any of the select()
47  * methods. The selector cannot be modified from another thread while
48  * blocking on a call to these methods.
49  *
50  * Once the selector is no longer used you must call the close() method so
51  * that the selector can free any resources it may have allocated in the call
52  * to open().
53  *
54  * See_Also: ISelector
55  *
56  * Examples:
57  * ---
58  * import tango.io.selector.model.ISelector;
59  * import tango.net.device.Socket;
60  * import tango.io.Stdout;
61  *
62  * AbstractSelector selector;
63  * SocketConduit conduit1;
64  * SocketConduit conduit2;
65  * MyClass object1;
66  * MyClass object2;
67  * uint eventCount;
68  *
69  * // Initialize the selector assuming that it will deal with 2 conduits and
70  * // will receive 2 events per invocation to the select() method.
71  * selector.open(2, 2);
72  *
73  * selector.register(conduit, Event.Read, object1);
74  * selector.register(conduit, Event.Write, object2);
75  *
76  * eventCount = selector.select();
77  *
78  * if (eventCount > 0)
79  * {
80  *     char[16] buffer;
81  *     int count;
82  *
83  *     foreach (SelectionKey key, selector.selectedSet())
84  *     {
85  *         if (key.isReadable())
86  *         {
87  *             count = (cast(SocketConduit) key.conduit).read(buffer);
88  *             if (count != IConduit.Eof)
89  *             {
90  *                 Stdout.format("Received '{0}' from peer\n", buffer[0..count]);
91  *                 selector.register(key.conduit, Event.Write, key.attachment);
92  *             }
93  *             else
94  *             {
95  *                 selector.unregister(key.conduit);
96  *                 key.conduit.close();
97  *             }
98  *         }
99  *
100  *         if (key.isWritable())
101  *         {
102  *             count = (cast(SocketConduit) key.conduit).write("MESSAGE");
103  *             if (count != IConduit.Eof)
104  *             {
105  *                 Stdout("Sent 'MESSAGE' to peer\n");
106  *                 selector.register(key.conduit, Event.Read, key.attachment);
107  *             }
108  *             else
109  *             {
110  *                 selector.unregister(key.conduit);
111  *                 key.conduit.close();
112  *             }
113  *         }
114  *
115  *         if (key.isError() || key.isHangup() || key.isInvalidHandle())
116  *         {
117  *             selector.unregister(key.conduit);
118  *             key.conduit.close();
119  *         }
120  *     }
121  * }
122  *
123  * selector.close();
124  * ---
125  */
126 abstract class AbstractSelector: ISelector
127 {
128     /**
129      * Restart interrupted system calls when blocking inside a call to select.
130      */
131     protected bool _restartInterruptedSystemCall = true;
132 
133     /**
134      * Indicates whether interrupted system calls will be restarted when
135      * blocking inside a call to select.
136      */
137     public bool restartInterruptedSystemCall()
138     {
139         return _restartInterruptedSystemCall;
140     }
141 
142     /**
143      * Sets whether interrupted system calls will be restarted when
144      * blocking inside a call to select.
145      */
146     public void restartInterruptedSystemCall(bool value)
147     {
148         _restartInterruptedSystemCall = value;
149     }
150 
151     /**
152      * Initialize the selector.
153      *
154      * Params:
155      * size         = value that provides a hint for the maximum amount of
156      *                conduits that will be registered
157      * maxEvents    = value that provides a hint for the maximum amount of
158      *                conduit events that will be returned in the selection
159      *                set per call to select.
160      */
161     public abstract void open(uint size, uint maxEvents);
162 
163     /**
164      * Free any operating system resources that may have been allocated in the
165      * call to open().
166      *
167      * Remarks:
168      * Not all of the selectors need to free resources other than allocated
169      * memory, but those that do will normally also add a call to close() in
170      * their destructors.
171      */
172     public abstract void close();
173 
174     /**
175      * Associate a conduit to the selector and track specific I/O events.
176      *
177      * Params:
178      * conduit      = conduit that will be associated to the selector
179      * events       = bit mask of Event values that represent the events that
180      *                will be tracked for the conduit.
181      * attachment   = optional object with application-specific data that will
182      *                be available when an event is triggered for the conduit
183      *
184      * Examples:
185      * ---
186      * AbstractSelector selector;
187      * SocketConduit conduit;
188      * MyClass object;
189      *
190      * selector.register(conduit, Event.Read | Event.Write, object);
191      * ---
192      */
193     public abstract void register(ISelectable conduit, Event events,
194                                   Object attachment);
195 
196     /**
197      * Deprecated, use register instead
198      */
199     deprecated public final void reregister(ISelectable conduit, Event events,
200             Object attachment = null)
201     {
202         register(conduit, events, attachment);
203     }
204 
205     /**
206      * Remove a conduit from the selector.
207      *
208      * Params:
209      * conduit      = conduit that had been previously associated to the
210      *                selector; it can be null.
211      *
212      * Remarks:
213      * Unregistering a null conduit is allowed and no exception is thrown
214      * if this happens.
215      */
216     public abstract void unregister(ISelectable conduit);
217 
218     /**
219      * Wait for I/O events from the registered conduits for a specified
220      * amount of time.
221      *
222      * Returns:
223      * The amount of conduits that have received events; 0 if no conduits
224      * have received events within the specified timeout; and -1 if the
225      * wakeup() method has been called from another thread.
226      *
227      * Remarks:
228      * This method is the same as calling select(TimeSpan.max).
229      */
230     public int select()
231     {
232         return select(TimeSpan.max);
233     }
234 
235     /**
236      * Wait for I/O events from the registered conduits for a specified
237      * amount of time.
238      *
239      * Note: This representation of timeout is not always accurate, so it is
240      * possible that the function will return with a timeout before the
241      * specified period.  For more accuracy, use the TimeSpan version.
242      *
243      * Params:
244      * timeout  = the maximum amount of time in seconds that the
245      *            selector will wait for events from the conduits; the
246      *            amount of time is relative to the current system time
247      *            (i.e. just the number of milliseconds that the selector
248      *            has to wait for the events).
249      *
250      * Returns:
251      * The amount of conduits that have received events; 0 if no conduits
252      * have received events within the specified timeout.
253      */
254     public int select(double timeout)
255     {
256             return select(TimeSpan.fromInterval(timeout));
257     }
258 
259     /**
260      * Wait for I/O events from the registered conduits for a specified
261      * amount of time.
262      *
263      * Params:
264      * timeout  = TimeSpan with the maximum amount of time that the
265      *            selector will wait for events from the conduits; the
266      *            amount of time is relative to the current system time
267      *            (i.e. just the number of milliseconds that the selector
268      *            has to wait for the events).
269      *
270      * Returns:
271      * The amount of conduits that have received events; 0 if no conduits
272      * have received events within the specified timeout; and -1 if the
273      * wakeup() method has been called from another thread.
274      */
275     public abstract int select(TimeSpan timeout);
276 
277     /**
278      * Causes the first call to select() that has not yet returned to return
279      * immediately.
280      *
281      * If another thread is currently blocked in an call to any of the
282      * select() methods then that call will return immediately. If no
283      * selection operation is currently in progress then the next invocation
284      * of one of these methods will return immediately. In any case the value
285      * returned by that invocation may be non-zero. Subsequent invocations of
286      * the select() methods will block as usual unless this method is invoked
287      * again in the meantime.
288      */
289     // public abstract void wakeup();
290 
291     /**
292      * Return the selection set resulting from the call to any of the select()
293      * methods.
294      *
295      * Remarks:
296      * If the call to select() was unsuccessful or it did not return any
297      * events, the returned value will be null.
298      */
299     public abstract ISelectionSet selectedSet();
300 
301     /**
302      * Return the selection key resulting from the registration of a conduit
303      * to the selector.
304      *
305      * Remarks:
306      * If the conduit is not registered to the selector the returned
307      * value will be null. No exception will be thrown by this method.
308      */
309     public abstract SelectionKey key(ISelectable conduit);
310 
311     /**
312      * Return the number of keys resulting from the registration of a conduit
313      * to the selector.
314      */
315     public abstract size_t count();
316 
317     /**
318      * Cast the time duration to a C timeval struct.
319     */
320     public timeval* toTimeval(timeval* tv, TimeSpan interval)
321     in
322     {
323         assert(tv !is null);
324     }
325     body
326     {
327         tv.tv_sec = cast(typeof(tv.tv_sec)) interval.seconds;
328         tv.tv_usec = cast(typeof(tv.tv_usec)) (interval.micros % 1_000_000);
329         return tv;
330     }
331 
332     /**
333      * Check the 'errno' global variable from the C standard library and
334      * throw an exception with the description of the error.
335      *
336      * Params:
337      * file     = Name of the source file where the check is being made; you
338      *            would normally use __FILE__ for this parameter.
339      * line     = Line number of the source file where this method was called;
340      *            you would normally use __LINE__ for this parameter.
341      *
342      * $(UL
343      * $(LI $(B RegisteredConduitException) when the conduit
344      *      should not be registered but it is (EEXIST).)
345      * $(LI $(B UnregisteredConduitException) when the conduit
346      *      should be registered but it isn't (ENOENT).)
347      * $(LI $(B InterruptedSystemCallException) when a system call has been
348      *      interrupted (EINTR).)
349      * $(LI $(B OutOfMemoryException) if a memory allocation fails (ENOMEM).)
350      * $(LI $(B SelectorException) for any of the other cases
351      *      in which errno is not 0.))
352      */
353     protected void checkErrno(string file, size_t line)
354     {
355         int errorCode = errno;
356         switch (errorCode)
357         {
358             case EBADF:
359                 throw new SelectorException("Bad file descriptor", file, line);
360                 // break;
361             case EEXIST:
362                 throw new RegisteredConduitException(file, line);
363                 // break;
364             case EINTR:
365                 throw new InterruptedSystemCallException(file, line);
366                 // break;
367             case EINVAL:
368                 throw new SelectorException("An invalid parameter was sent to a system call", file, line);
369                 // break;
370             case ENFILE:
371                 throw new SelectorException("Maximum number of open files reached", file, line);
372                 // break;
373             case ENOENT:
374                 throw new UnregisteredConduitException(file, line);
375                 // break;
376             case ENOMEM:
377                 throw new OutOfMemoryException(file, line);
378                 // break;
379             case EPERM:
380                 throw new SelectorException("The conduit cannot be used with this Selector", file, line);
381                 // break;
382             default:
383                 throw new SelectorException("Unknown Selector error: " ~ SysError.lookup(errorCode).idup, file, line);
384                 // break;
385         }
386     }
387 }