132
|
1 /*******************************************************************************
|
|
2 copyright: Copyright (c) 2006 Juan Jose Comellas. All rights reserved
|
|
3 license: BSD style: $(LICENSE)
|
|
4 author: Juan Jose Comellas <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
|
|
14 private import tango.io.selector.model.ISelector;
|
|
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.io.Stdout;
|
|
38 * import tango.net.SocketConduit;
|
|
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.reregister(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.reregister(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 const uint DefaultSize = 64;
|
|
117 /**
|
|
118 * Default maximum number of events that will be received per
|
|
119 * invocation to select().
|
|
120 */
|
|
121 public const 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 /** Number of events resulting from the call to select() */
|
|
134 private int _eventCount = 0;
|
|
135
|
|
136
|
|
137 /**
|
|
138 * Destructor
|
|
139 */
|
|
140 ~this()
|
|
141 {
|
|
142 // Make sure that we release the epoll file descriptor once this
|
|
143 // object is garbage collected.
|
|
144 close();
|
|
145 }
|
|
146
|
|
147 /**
|
|
148 * Open the epoll selector, makes a call to epoll_create()
|
|
149 *
|
|
150 * Params:
|
|
151 * size = maximum amount of conduits that will be registered;
|
|
152 * it will grow dynamically if needed.
|
|
153 * maxEvents = maximum amount of conduit events that will be
|
|
154 * returned in the selection set per call to select();
|
|
155 * this limit is enforced by this selector.
|
|
156 *
|
|
157 * Throws:
|
|
158 * SelectorException if there are not enough resources to open the
|
|
159 * selector (e.g. not enough file handles or memory available).
|
|
160 */
|
|
161 public void open(uint size = DefaultSize, uint maxEvents = DefaultMaxEvents)
|
|
162 in
|
|
163 {
|
|
164 assert(size > 0);
|
|
165 assert(maxEvents > 0);
|
|
166 }
|
|
167 body
|
|
168 {
|
|
169 _events = new epoll_event[maxEvents];
|
|
170
|
|
171 _epfd = epoll_create(cast(int) size);
|
|
172 if (_epfd < 0)
|
|
173 {
|
|
174 checkErrno(__FILE__, __LINE__);
|
|
175 }
|
|
176 }
|
|
177
|
|
178 /**
|
|
179 * Close the selector, releasing the file descriptor that had been
|
|
180 * created in the previous call to open().
|
|
181 *
|
|
182 * Remarks:
|
|
183 * It can be called multiple times without harmful side-effects.
|
|
184 */
|
|
185 public void close()
|
|
186 {
|
|
187 if (_epfd >= 0)
|
|
188 {
|
|
189 .close(_epfd);
|
|
190 _epfd = -1;
|
|
191 }
|
|
192 _events = null;
|
|
193 _eventCount = 0;
|
|
194 }
|
|
195
|
|
196 /**
|
|
197 * Associate a conduit to the selector and track specific I/O events.
|
|
198 *
|
|
199 * Params:
|
|
200 * conduit = conduit that will be associated to the selector;
|
|
201 * must be a valid conduit (i.e. not null and open).
|
|
202 * events = bit mask of Event values that represent the events
|
|
203 * that will be tracked for the conduit.
|
|
204 * attachment = optional object with application-specific data that
|
|
205 * will be available when an event is triggered for the
|
|
206 * conduit
|
|
207 *
|
|
208 * Throws:
|
|
209 * RegisteredConduitException if the conduit had already been
|
|
210 * registered to the selector; SelectorException if there are not
|
|
211 * enough resources to add the conduit to the selector.
|
|
212 *
|
|
213 * Examples:
|
|
214 * ---
|
|
215 * selector.register(conduit, Event.Read | Event.Write, object);
|
|
216 * ---
|
|
217 */
|
|
218 public void register(ISelectable conduit, Event events, Object attachment = null)
|
|
219 in
|
|
220 {
|
|
221 assert(conduit !is null && conduit.fileHandle() >= 0);
|
|
222 }
|
|
223 body
|
|
224 {
|
|
225 epoll_event event;
|
|
226 SelectionKey key = new SelectionKey(conduit, events, attachment);
|
|
227
|
|
228 event.events = events;
|
|
229 // We associate the selection key to the epoll_event to be able to
|
|
230 // retrieve it efficiently when we get events for this handle.
|
|
231 event.data.ptr = cast(void*) key;
|
|
232
|
|
233 if (epoll_ctl(_epfd, EPOLL_CTL_ADD, conduit.fileHandle(), &event) == 0)
|
|
234 {
|
|
235 // We keep the keys in a map to make sure that the key is not
|
|
236 // garbage collected while there is still a reference to it in
|
|
237 // an epoll_event. This also allows to to efficiently find the
|
|
238 // key corresponding to a handle in methods where this
|
|
239 // association is not provided automatically.
|
|
240 _keys[conduit.fileHandle()] = key;
|
|
241 }
|
|
242 else
|
|
243 {
|
|
244 checkErrno(__FILE__, __LINE__);
|
|
245 }
|
|
246 }
|
|
247
|
|
248 /**
|
|
249 * Modify the events that are being tracked or the 'attachment' field
|
|
250 * for an already registered conduit.
|
|
251 *
|
|
252 * Params:
|
|
253 * conduit = conduit that will be associated to the selector;
|
|
254 * must be a valid conduit (i.e. not null and open).
|
|
255 * events = bit mask of Event values that represent the events
|
|
256 * that will be tracked for the conduit.
|
|
257 * attachment = optional object with application-specific data that
|
|
258 * will be available when an event is triggered for the
|
|
259 * conduit
|
|
260 *
|
|
261 * Remarks:
|
|
262 * The 'attachment' member of the SelectionKey will always be
|
|
263 * overwritten, even if it's null.
|
|
264 *
|
|
265 * Throws:
|
|
266 * UnregisteredConduitException if the conduit had not been previously
|
|
267 * registered to the selector; SelectorException if there are not
|
|
268 * enough resources to modify the conduit registration.
|
|
269 *
|
|
270 * Examples:
|
|
271 * ---
|
|
272 * selector.reregister(conduit, Event.Write, object);
|
|
273 * ---
|
|
274 */
|
|
275 public void reregister(ISelectable conduit, Event events, Object attachment = null)
|
|
276 in
|
|
277 {
|
|
278 assert(conduit !is null && conduit.fileHandle() >= 0);
|
|
279 }
|
|
280 body
|
|
281 {
|
|
282 SelectionKey* key = (conduit.fileHandle() in _keys);
|
|
283
|
|
284 if (key !is null)
|
|
285 {
|
|
286 epoll_event event;
|
|
287
|
|
288 (*key).events = events;
|
|
289 (*key).attachment = attachment;
|
|
290
|
|
291 event.events = events;
|
|
292 event.data.ptr = cast(void*) *key;
|
|
293
|
|
294 if (epoll_ctl(_epfd, EPOLL_CTL_MOD, conduit.fileHandle(), &event) != 0)
|
|
295 {
|
|
296 checkErrno(__FILE__, __LINE__);
|
|
297 }
|
|
298 }
|
|
299 else
|
|
300 {
|
|
301 throw new UnregisteredConduitException(__FILE__, __LINE__);
|
|
302 }
|
|
303 }
|
|
304
|
|
305 /**
|
|
306 * Remove a conduit from the selector.
|
|
307 *
|
|
308 * Params:
|
|
309 * conduit = conduit that had been previously associated to the
|
|
310 * selector; it can be null.
|
|
311 *
|
|
312 * Remarks:
|
|
313 * Unregistering a null conduit is allowed and no exception is thrown
|
|
314 * if this happens.
|
|
315 *
|
|
316 * Throws:
|
|
317 * UnregisteredConduitException if the conduit had not been previously
|
|
318 * registered to the selector; SelectorException if there are not
|
|
319 * enough resources to remove the conduit registration.
|
|
320 */
|
|
321 public void unregister(ISelectable conduit)
|
|
322 {
|
|
323 if (conduit !is null)
|
|
324 {
|
|
325 if (epoll_ctl(_epfd, EPOLL_CTL_DEL, conduit.fileHandle(), null) == 0)
|
|
326 {
|
|
327 _keys.remove(conduit.fileHandle());
|
|
328 }
|
|
329 else
|
|
330 {
|
|
331 checkErrno(__FILE__, __LINE__);
|
|
332 }
|
|
333 }
|
|
334 }
|
|
335
|
|
336 /**
|
|
337 * Wait for I/O events from the registered conduits for a specified
|
|
338 * amount of time.
|
|
339 *
|
|
340 * Params:
|
|
341 * timeout = TimeSpan with the maximum amount of time that the
|
|
342 * selector will wait for events from the conduits; the
|
|
343 * amount of time is relative to the current system time
|
|
344 * (i.e. just the number of milliseconds that the selector
|
|
345 * has to wait for the events).
|
|
346 *
|
|
347 * Returns:
|
|
348 * The amount of conduits that have received events; 0 if no conduits
|
|
349 * have received events within the specified timeout; and -1 if the
|
|
350 * wakeup() method has been called from another thread.
|
|
351 *
|
|
352 * Throws:
|
|
353 * InterruptedSystemCallException if the underlying system call was
|
|
354 * interrupted by a signal and the 'restartInterruptedSystemCall'
|
|
355 * property was set to false; SelectorException if there were no
|
|
356 * resources available to wait for events from the conduits.
|
|
357 */
|
|
358 public int select(TimeSpan timeout)
|
|
359 {
|
|
360 int to = (timeout != TimeSpan.max ? cast(int) timeout.millis : -1);
|
|
361
|
|
362 while (true)
|
|
363 {
|
|
364 // FIXME: add support for the wakeup() call.
|
|
365 _eventCount = epoll_wait(_epfd, _events.ptr, _events.length, to);
|
|
366 if (_eventCount >= 0)
|
|
367 {
|
|
368 break;
|
|
369 }
|
|
370 else
|
|
371 {
|
|
372 if (errno != EINTR || !_restartInterruptedSystemCall)
|
|
373 {
|
|
374 checkErrno(__FILE__, __LINE__);
|
|
375 }
|
|
376 debug (selector)
|
|
377 Stdout("--- Restarting epoll_wait() after being interrupted by a signal\n");
|
|
378 }
|
|
379 }
|
|
380 return _eventCount;
|
|
381 }
|
|
382
|
|
383 /**
|
|
384 * Return the selection set resulting from the call to any of the
|
|
385 * select() methods.
|
|
386 *
|
|
387 * Remarks:
|
|
388 * If the call to select() was unsuccessful or it did not return any
|
|
389 * events, the returned value will be null.
|
|
390 */
|
|
391 public ISelectionSet selectedSet()
|
|
392 {
|
|
393 return (_eventCount > 0 ? new EpollSelectionSet(_events[0.._eventCount]) : null);
|
|
394 }
|
|
395
|
|
396 /**
|
|
397 * Return the selection key resulting from the registration of a
|
|
398 * conduit to the selector.
|
|
399 *
|
|
400 * Remarks:
|
|
401 * If the conduit is not registered to the selector the returned
|
|
402 * value will be null. No exception will be thrown by this method.
|
|
403 */
|
|
404 public SelectionKey key(ISelectable conduit)
|
|
405 {
|
|
406 return (conduit !is null ? _keys[conduit.fileHandle()] : null);
|
|
407 }
|
|
408
|
|
409 unittest
|
|
410 {
|
|
411 }
|
|
412 }
|
|
413
|
|
414 /**
|
|
415 * Class used to hold the list of Conduits that have received events.
|
|
416 */
|
|
417 private class EpollSelectionSet: ISelectionSet
|
|
418 {
|
|
419 private epoll_event[] _events;
|
|
420
|
|
421 protected this(epoll_event[] events)
|
|
422 {
|
|
423 _events = events;
|
|
424 }
|
|
425
|
|
426 public uint length()
|
|
427 {
|
|
428 return _events.length;
|
|
429 }
|
|
430
|
|
431 /**
|
|
432 * Iterate over all the Conduits that have received events.
|
|
433 */
|
|
434 public int opApply(int delegate(inout SelectionKey) dg)
|
|
435 {
|
|
436 int rc = 0;
|
|
437 SelectionKey key;
|
|
438
|
|
439 debug (selector)
|
|
440 Stdout.format("--- EpollSelectionSet.opApply() ({0} events)\n", _events.length);
|
|
441
|
|
442 foreach (epoll_event event; _events)
|
|
443 {
|
|
444 // Only invoke the delegate if there is an event for the conduit.
|
|
445 if (event.events != 0)
|
|
446 {
|
|
447 key = cast(SelectionKey) event.data.ptr;
|
|
448 key.events = cast(Event) event.events;
|
|
449
|
|
450 debug (selector)
|
|
451 Stdout.format("--- Event 0x{0:x} for handle {1}\n",
|
|
452 cast(uint) event.events, cast(int) key.conduit.fileHandle());
|
|
453
|
|
454 rc = dg(key);
|
|
455 if (rc != 0)
|
|
456 {
|
|
457 break;
|
|
458 }
|
|
459 }
|
|
460 }
|
|
461 return rc;
|
|
462 }
|
|
463 }
|
|
464 }
|
|
465
|