1 /*******************************************************************************
2 
3     Application extension for handling user-defined timed or repeating events.
4 
5     Internally, the extension uses a timer set to manage the set of timed
6     events. The internal timer set's TimerEvent instance is registered with
7     epoll when one or more timed events are registered. When no timed events are
8     registered, the TimerEvent is not registered.
9 
10     Due to its internal use of epoll, this extension requires an epoll instance
11     to be passed to its constructor. This is unlike the SignalExt, which the
12     user must manually register with epoll.
13 
14     Usage example:
15         See documented unittest below.
16 
17     Copyright:
18         Copyright (c) 2009-2016 dunnhumby Germany GmbH.
19         All rights reserved.
20 
21     License:
22         Boost Software License Version 1.0. See LICENSE_BOOST.txt for details.
23         Alternatively, this file may be distributed under the terms of the Tango
24         3-Clause BSD License (see LICENSE_BSD.txt for details).
25 
26 *******************************************************************************/
27 
28 module ocean.util.app.ext.TimerExt;
29 
30 import ocean.core.Verify;
31 
32 
33 /*******************************************************************************
34 
35     Example of using the TimerExt in an application class.
36 
37 *******************************************************************************/
38 
39 version (unittest)
40 {
41     import ocean.util.app.Application;
42 }
43 
44 ///
45 unittest
46 {
47     class App : Application
48     {
49         import ocean.io.select.EpollSelectDispatcher;
50         import ocean.meta.types.Qualifiers;
51 
52         private EpollSelectDispatcher epoll;
53         private TimerExt timers;
54 
55         public this ( )
56         {
57             super("", "");
58 
59             this.epoll = new EpollSelectDispatcher;
60             this.timers = new TimerExt(this.epoll);
61             this.registerExtension(this.timers);
62         }
63 
64         override protected int run ( istring[] args )
65         {
66             // Register some timed events
67             this.timers.register(&this.first, 0.0001);
68             this.timers.register(&this.second, 0.0002);
69             this.timers.register(&this.third, 0.0003);
70 
71             this.epoll.eventLoop();
72 
73             return 0;
74         }
75 
76         private bool first ( )
77         {
78             return false; // false means that the event will not be reregistered
79         }
80 
81         private bool second ( )
82         {
83             return false;
84         }
85 
86         private bool third ( )
87         {
88             return false;
89         }
90     }
91 }
92 
93 
94 
95 import ocean.meta.types.Qualifiers;
96 import ocean.util.app.model.IApplicationExtension;
97 
98 
99 public class TimerExt : IApplicationExtension
100 {
101     import ocean.application.components.Timers;
102     import ocean.util.app.Application;
103 
104     import ocean.io.select.EpollSelectDispatcher;
105     import ocean.io.select.client.TimerSet;
106     import BucketElementFreeList = ocean.util.container.map.model.BucketElementFreeList;
107     import ocean.time.MicrosecondsClock;
108     import ocean.time.timeout.TimeoutManager;
109     import ocean.util.log.Logger;
110 
111     /***************************************************************************
112 
113         Type of delegate called when an event fires. The delegate's return value
114         indicates whether the timed event should remain registered (true) or be
115         unregistered (false).
116 
117     ***************************************************************************/
118 
119     public alias Timers.EventDg EventDg;
120 
121     /***************************************************************************
122 
123         Set of application timers.
124 
125     ***************************************************************************/
126 
127     private Timers timer_set;
128 
129     /***************************************************************************
130 
131         Constructor. Creates the internal event timer set.
132 
133         Params:
134             epoll = select dispatcher with which to register the timer set
135 
136     ***************************************************************************/
137 
138     public this ( EpollSelectDispatcher epoll )
139     {
140         this.timer_set = new Timers(epoll);
141     }
142 
143     /***************************************************************************
144 
145         Registers a timer with the extension. The provided delegate will be
146         called repeatedly according to the specified period, as long as it
147         returns true.
148 
149         Params:
150             dg = delegate to call periodically
151             period_s = seconds between calls of the delegate
152 
153     ***************************************************************************/
154 
155     public void register ( scope EventDg dg, double period_s )
156     {
157         this.timer_set.register(dg, period_s);
158     }
159 
160     /***************************************************************************
161 
162         Registers a timer with the extension. The provided delegate will be
163         called once after the initial delay specified, then repeatedly according
164         to the specified period, as long as it returns true.
165 
166         Params:
167             dg = delegate to call periodically
168             init_s = seconds before initial call of the delegate
169             period_s = seconds between subsequent calls of the delegate
170 
171     ***************************************************************************/
172 
173     public void register ( scope EventDg dg, double init_s, double period_s )
174     {
175         this.timer_set.register(dg, init_s, period_s);
176     }
177 
178     /***************************************************************************
179 
180         Registers a timer with the extension. The provided delegate will be
181         called once after the initial delay specified, then repeatedly according
182         to the specified period, as long as it returns true.
183 
184         Note that this internal method is called both from the public register()
185         methods and the private eventFired().
186 
187         Params:
188             dg = delegate to call periodically
189             init_microsec = microseconds before initial call of the delegate
190             period_microsec = microseconds between subsequent calls of the
191                 delegate
192 
193     ***************************************************************************/
194 
195     public void registerMicrosec ( scope EventDg dg, ulong init_microsec, ulong period_microsec )
196     {
197         this.timer_set.registerMicrosec(dg, init_microsec, period_microsec);
198     }
199 
200     /***************************************************************************
201 
202         Unregisters all timed events (thus unregisters the internal TimerEvent
203         from epoll).
204 
205     ***************************************************************************/
206 
207     public void clear ( )
208     {
209         this.timer_set.clear();
210     }
211 
212     /***************************************************************************
213 
214         Returns:
215             the extension order
216 
217     ***************************************************************************/
218 
219     public override int order ( )
220     {
221         return -1;
222     }
223 
224     /***************************************************************************
225 
226         Unused IApplicationExtension methods.
227 
228         We just need to provide an "empty" implementation to satisfy the
229         interface.
230 
231     ***************************************************************************/
232 
233     public override void preRun ( IApplication app, istring[] args )
234     {
235     }
236 
237     /// ditto
238     public override void postRun ( IApplication app, istring[] args, int status )
239     {
240     }
241 
242     /// ditto
243     public override void atExit ( IApplication app, istring[] args, int status,
244             ExitException exception )
245     {
246     }
247 
248     /// ditto
249     public override ExitException onExitException ( IApplication app, istring[] args,
250             ExitException exception )
251     {
252         return exception;
253     }
254 }
255 
256 
257 version (unittest)
258 {
259     import ocean.util.app.Application;
260 
261     import ocean.core.Test;
262 }
263 
264 
265 /*******************************************************************************
266 
267     Test that scheduled events are called in the correct order.
268 
269 *******************************************************************************/
270 
271 version(none) // current test is flakey and needs to be reworked
272 unittest
273 {
274     class App
275     {
276         import ocean.io.select.EpollSelectDispatcher;
277 
278         private EpollSelectDispatcher epoll;
279 
280         private uint counter;
281 
282         this ( )
283         {
284             this.epoll = new EpollSelectDispatcher;
285             auto timers = new TimerExt(this.epoll);
286 
287             // Register some events
288             timers.register(&this.first, 0.0001, 10);
289             timers.register(&this.second, 0.0002, 10);
290             timers.register(&this.third, 0.0003, 10);
291 
292             // Run timers until all are unregistered
293             this.epoll.eventLoop();
294 
295             // When the event loop exits, all three should have fired
296             test!("==")(this.counter, 3);
297         }
298 
299         private bool first ( )
300         {
301             test!("==")(this.counter, 0);
302             this.counter++;
303             return false;
304         }
305 
306         private bool second ( )
307         {
308             test!("==")(this.counter, 1);
309             this.counter++;
310             return false;
311         }
312 
313         private bool third ( )
314         {
315             test!("==")(this.counter, 2);
316             this.counter++;
317             return false;
318         }
319     }
320 }
321 
322 /*******************************************************************************
323 
324     Test clearing registered events.
325 
326 *******************************************************************************/
327 
328 version(none) // current test is flakey and needs to be reworked
329 unittest
330 {
331     class App
332     {
333         import ocean.io.select.EpollSelectDispatcher;
334 
335         private EpollSelectDispatcher epoll;
336 
337         private TimerExt timers;
338 
339         private uint counter;
340 
341         this ( )
342         {
343             this.epoll = new EpollSelectDispatcher;
344             this.timers = new TimerExt(this.epoll);
345 
346             // Register some events
347             this.timers.register(&this.first, 0.0001, 10);
348             this.timers.register(&this.second, 0.0002, 10);
349 
350             // Run timers until first() clears them all
351             this.epoll.eventLoop();
352 
353             // When the event loop exits, only the first should have fired
354             test!("==")(this.counter, 1);
355         }
356 
357         private bool first ( )
358         {
359             test!("==")(this.counter, 0);
360             this.counter++;
361 
362             test!("==")(this.timers.timer_set.length, 1);
363             this.timers.clear();
364             test!("==")(this.timers.timer_set.length, 0);
365 
366             return false;
367         }
368 
369         private bool second ( )
370         {
371             assert(false);
372             return false;
373         }
374     }
375 }
376 
377 /*******************************************************************************
378 
379     Test for unregistering a repeated timer.
380 
381 *******************************************************************************/
382 
383 version(none) // current test is flakey and needs to be reworked
384 unittest
385 {
386     class App
387     {
388         import ocean.io.select.EpollSelectDispatcher;
389 
390         private EpollSelectDispatcher epoll;
391 
392         private uint counter;
393 
394         this ( )
395         {
396             this.epoll = new EpollSelectDispatcher;
397             auto timers = new TimerExt(this.epoll);
398 
399             // Register an event
400             timers.register(&this.dg, 0.0001);
401 
402             // Run timers until dg() returns false
403             this.epoll.eventLoop();
404 
405             // When the event loop exits, dg() should have fired three times
406             test!("==")(this.counter, 3);
407         }
408 
409         private bool dg ( )
410         {
411             this.counter++;
412             test!("<=")(this.counter, 3);
413             return this.counter < 3;
414         }
415     }
416 }