1 /*******************************************************************************
2 
3     Epoll-based event scheduler.
4 
5     Multiplexes multiple, logical timers into a single timer fd (see
6     ocean.io.select.client.TimerEvent).
7 
8     copyright:  Copyright (c) 2011 dunnhumby Germany GmbH.
9                 All rights reserved.
10 
11     version:    August 2011 : Initial release
12 
13     authors:    Gavin Norman
14 
15     Requires linking with libebtree:
16 
17     ---
18 
19         -L-lebtree
20 
21     ---
22 
23     Usage example:
24         See documented unittest of class, below
25 
26 *******************************************************************************/
27 
28 module ocean.io.select.client.TimerSet;
29 
30 
31 
32 
33 import ocean.util.container.pool.ObjectPool;
34 
35 import ocean.core.Verify;
36 
37 import ocean.task.IScheduler;
38 import ocean.io.select.EpollSelectDispatcher;
39 
40 import ocean.io.select.timeout.TimerEventTimeoutManager;
41 
42 import ocean.time.timeout.model.ITimeoutClient;
43 
44 import ocean.meta.types.Qualifiers;
45 import ocean.util.container.map.model.IAllocator;
46 debug import ocean.text.convert.Formatter;
47 
48 
49 /*******************************************************************************
50 
51     Timer set class template.
52 
53     Each event is added to the timer set along with a data item (see template
54     parameters). The data item is stored internally to the timer set, along with
55     the event delegate, as a convenience to the end-user, who thus does not need
56     to maintain an external pool of the data items associated with each event.
57 
58     Internally, the timer set works using a single timer event, which is
59     registered to epoll with the time until the soonest scheduled event. When
60     the last scheduled event fires, the timer event is unregistered from epoll.
61 
62     Params:
63         EventData = type of data to be stored along with each event
64 
65     TODO: could probably be adapted to a version which allows simple events
66     without attached data, if we need that.
67 
68 *******************************************************************************/
69 
70 public class TimerSet ( EventData ) : TimerEventTimeoutManager
71 {
72     /***************************************************************************
73 
74         Alias for a delegate which is called when a scheduled event fires.
75 
76     ***************************************************************************/
77 
78     public alias void delegate ( ref EventData data ) EventFiredDg;
79 
80 
81     /***************************************************************************
82 
83         Alias for a delegate which is called when a scheduled event is
84         registered, allowing the user to setup the required data for the event.
85 
86     ***************************************************************************/
87 
88     public alias void delegate ( ref EventData data ) EventSetupDg;
89 
90 
91     /***************************************************************************
92 
93         Interface to a scheduled event.
94 
95     ***************************************************************************/
96 
97     public interface IEvent
98     {
99         /***********************************************************************
100 
101             Unregisters this event.
102 
103         ***********************************************************************/
104 
105         void unregister ( );
106     }
107 
108     /***************************************************************************
109 
110         Internal event class.
111 
112     ***************************************************************************/
113 
114     private class Event : ITimeoutClient, IEvent
115     {
116         /***********************************************************************
117 
118             Instance identifier, used by id() method in debug.
119 
120         ***********************************************************************/
121 
122         debug static int id_num_;
123 
124         debug int id_num;
125 
126 
127         /***********************************************************************
128 
129             Index of this event in the event pool (required by Pool).
130 
131         ***********************************************************************/
132 
133         public size_t object_pool_index;
134 
135 
136         /***********************************************************************
137 
138             Data associated with this event.
139 
140         ***********************************************************************/
141 
142         public EventData data;
143 
144 
145         /***********************************************************************
146 
147             Delegate to call when this event fires.
148 
149         ***********************************************************************/
150 
151         public EventFiredDg fired_dg;
152 
153 
154         /***********************************************************************
155 
156             Registration of this event in the timeout manager.
157 
158         ***********************************************************************/
159 
160         private ExpiryRegistration expiry_registration;
161 
162 
163         /***********************************************************************
164 
165             Constructor.
166 
167         ***********************************************************************/
168 
169         public this ( )
170         {
171             debug this.id_num = id_num_++;
172 
173             this.expiry_registration = new ExpiryRegistration(this);
174         }
175 
176 
177         /***********************************************************************
178 
179             Registers this event to fire in the specified number of
180             microseconds.
181 
182             Params:
183                 schedule_us = (minimum) microseconds before event will fire
184 
185         ***********************************************************************/
186 
187         public void register ( ulong schedule_us )
188         {
189             this.expiry_registration.register(schedule_us);
190         }
191 
192 
193         /***********************************************************************
194 
195             Unregisters this event.
196 
197         ***********************************************************************/
198 
199         public void unregister ( )
200         {
201             this.expiry_registration.drop();
202             this.outer.events.recycle(this);
203         }
204 
205 
206         /***********************************************************************
207 
208             ITimeoutClient interface method. Invoked when the client times out.
209             Calls the fired delegate and returns this event to the event pool.
210 
211         ***********************************************************************/
212 
213         public void timeout ( )
214         {
215             auto fired_dg = this.fired_dg;
216             auto data = this.data;
217             this.outer.events.recycle(this);
218             fired_dg(data);
219         }
220 
221 
222         /***********************************************************************
223 
224             String identifier for debugging.
225 
226         ***********************************************************************/
227 
228         debug
229         {
230             private mstring id_buf;
231 
232             protected cstring id ( )
233             {
234                 this.id_buf.length = 0;
235                 assumeSafeAppend(this.id_buf);
236                 sformat(this.id_buf, "Scheduler.Event {}", this.id_num);
237                 return this.id_buf;
238             }
239         }
240     }
241 
242 
243     /***************************************************************************
244 
245         Epoll select dispatcher used to manage the scheduler. Passed as a
246         reference to the constructor.
247 
248     ***************************************************************************/
249 
250     private EpollSelectDispatcher epoll;
251 
252 
253     /***************************************************************************
254 
255         Re-usable pool of scheduled events.
256 
257     ***************************************************************************/
258 
259     protected ObjectPool!(Event) events;
260 
261     /***************************************************************************
262 
263         Constructor.
264 
265         Params:
266             epoll = epoll select dispatcher to use. If null is supplied, the
267                 global `theScheduler.epoll()` instance will be used instead
268                 (see `schedule()` / `stopTimeout()`)
269             max_events = limit on the number of events which can be managed by
270                 the scheduler at one time. (0 = no limit)
271             allocator = use this bucket element allocator for the expiry
272                 registration to ISelectClient map. If it is null the default map
273                 allocator (BucketElementGCAllocator) is used.
274 
275     ***************************************************************************/
276 
277     public this ( EpollSelectDispatcher epoll = null, uint max_events = 0,
278         IAllocator allocator = null )
279     {
280         super(allocator);
281 
282         this.epoll = epoll;
283 
284         this.events = new ObjectPool!(Event);
285 
286         if ( max_events )
287         {
288             this.events.setLimit(max_events);
289         }
290     }
291 
292 
293     /***************************************************************************
294 
295         Registers a new event with the scheduler.
296 
297         Params:
298             setup_dg = delegate called to initialise event's associated data
299             fired_dg = delegate called when event fires
300             schedule_us = (minimum) microseconds before event will fire
301 
302         Throws:
303             The pool throws a LimitExceededException if the event pool is full
304             and the new event cannot be scheduled.
305 
306         Returns:
307             the newly scheduled event
308 
309     ***************************************************************************/
310 
311     public IEvent schedule ( scope EventSetupDg setup_dg, scope EventFiredDg fired_dg,
312         ulong schedule_us )
313     out ( event )
314     {
315         assert(event !is null);
316     }
317     do
318     {
319         verify(setup_dg !is null, typeof(this).stringof ~ ".schedule: event setup delegate is null");
320         verify(fired_dg !is null, typeof(this).stringof ~ ".schedule: event fired delegate is null");
321 
322         auto event = this.events.get(new Event);
323         event.fired_dg = fired_dg;
324 
325         setup_dg(event.data);
326 
327         if ( schedule_us )
328         {
329             event.register(schedule_us);
330             if (this.epoll is null)
331                 theScheduler.epoll().register(this.select_client);
332             else
333                 this.epoll.register(this.select_client);
334         }
335         else
336         {
337             event.timeout();
338         }
339 
340         return event;
341     }
342 
343 
344     /***************************************************************************
345 
346         Returns:
347             number of currently scheduled events
348 
349         Note: this method is aliased as 'length'
350 
351     ***************************************************************************/
352 
353     public size_t scheduled_events ( )
354     {
355         return this.events.length;
356     }
357 
358     public alias scheduled_events length;
359 
360 
361     /***************************************************************************
362 
363         Unregisters all registered events (thus calls stopTimeout()).
364 
365     ***************************************************************************/
366 
367     public void clear ( )
368     {
369         scope iterator = this.events..new BusyItemsIterator;
370         foreach ( event; iterator )
371         {
372             event.unregister();
373         }
374         this.events.clear();
375     }
376 
377     /***************************************************************************
378 
379         Disables the timer event and unregisters it from epoll.
380 
381     ***************************************************************************/
382 
383     override protected void stopTimeout ( )
384     {
385         super.stopTimeout();
386         if (this.epoll is null)
387             theScheduler.epoll().unregister(this.select_client);
388         else
389             this.epoll.unregister(this.select_client);
390     }
391 }
392 
393 version (unittest)
394 {
395     import ocean.io.Stdout;
396     import ocean.io.select.EpollSelectDispatcher;
397 }
398 
399 /// Usage example:
400 unittest
401 {
402     void timerSetTest ( )
403     {
404         // Struct associated with each event.
405         struct Params
406         {
407             int x;
408         }
409 
410         // Delegate called when an event fired. Receives the associated struct.
411         void fired ( ref Params p )
412         {
413             Stdout.formatln("{} fired", p.x);
414         }
415 
416         // Construct required objects.
417         auto epoll = new EpollSelectDispatcher;
418         auto timer_set = new TimerSet!(Params)(epoll);
419 
420         // Schedule some events.
421         timer_set.schedule((ref Params p){p.x = 0;}, &fired, 2_000_000);
422         timer_set.schedule((ref Params p){p.x = 1;}, &fired, 4_000_000);
423         timer_set.schedule((ref Params p){p.x = 2;}, &fired, 6_000_000);
424 
425         // Set everything going by starting the epoll event loop.
426         Stdout.formatln("Starting eventloop");
427         epoll.eventLoop;
428         Stdout.formatln("Event loop finished");
429     }
430 }
431 
432 unittest
433 {
434     // create instance to check if it compiles
435     class Dummy { }
436     TimerSet!(Dummy) timer_set;
437 }