1 /*******************************************************************************
2 
3     Timer event which can be registered with the EpollSelectDispatcher.
4 
5     Note that the unittest in this module requires linking with librt.
6 
7     Copyright:
8         Copyright (c) 2009-2016 dunnhumby Germany GmbH.
9         All rights reserved.
10 
11     License:
12         Boost Software License Version 1.0. See LICENSE_BOOST.txt for details.
13         Alternatively, this file may be distributed under the terms of the Tango
14         3-Clause BSD License (see LICENSE_BSD.txt for details).
15 
16 *******************************************************************************/
17 
18 module ocean.io.select.client.TimerEvent;
19 
20 
21 import ocean.io.select.client.model.ISelectClient: ISelectClient;
22 
23 import ocean.sys.TimerFD;
24 import ocean.core.Verify;
25 
26 import ocean.meta.types.Qualifiers;
27 import ocean.io.model.IConduit: ISelectable;
28 
29 import core.sys.posix.time: time_t, timespec, itimerspec;
30 
31 /*******************************************************************************
32 
33     TimerEvent class, calls the provided delegate when the timer fires and is
34     handled in epoll.
35 
36     Usage example:
37         See documented unittest after class
38 
39 *******************************************************************************/
40 
41 class TimerEvent : ITimerEvent
42 {
43     /***************************************************************************
44 
45         Alias for event handler delegate.
46 
47         Returns:
48             true to stay registered in epoll or false to unregister.
49 
50     ***************************************************************************/
51 
52     public alias bool delegate ( ) Handler;
53 
54     /***************************************************************************
55 
56         Event handler delegate.
57 
58     ***************************************************************************/
59 
60     private Handler handler;
61 
62     /***********************************************************************
63 
64         Constructor. Creates a file descriptor to manage the event.
65 
66         Constructor. Creates a custom event and hooks it up to the provided
67         event handler.
68 
69         Params:
70             handler = event handler
71 
72     ***********************************************************************/
73 
74     public this ( scope Handler handler, bool realtime = false )
75     {
76         super(realtime);
77 
78         this.handler = handler;
79     }
80 
81     /***************************************************************************
82 
83         Called from the select dispatcher when the event fires. Calls the user-
84         provided event handler.
85 
86         Params:
87             event = select event which fired, must be Read
88 
89         Returns:
90             forwards return value of event handler -- false indicates that the
91             event should be unregistered with the selector, true indicates that
92             it should remain registered and able to fire again
93 
94     ***************************************************************************/
95 
96     protected override bool handle_ ( ulong n )
97     {
98         verify(this.handler !is null);
99         return this.handler();
100     }
101 
102     /***********************************************************************
103 
104         Set a new handler
105 
106         Params:
107             handler = event handler
108 
109     ***********************************************************************/
110 
111     public void setHandler ( scope Handler handler )
112     {
113         this.handler = handler;
114     }
115 }
116 
117 version (unittest)
118 {
119     import ocean.io.select.EpollSelectDispatcher;
120 }
121 
122 /// TimerEvent usage example
123 unittest
124 {
125     // Delegate which will be called each time the timer fires and is handled in
126     // epoll
127     bool my_timer_dg ( )
128     {
129         return true; // to keep timer registered (false to unregister)
130     }
131 
132     auto epoll = new EpollSelectDispatcher;
133     auto timer = new TimerEvent(&my_timer_dg);
134     timer.set(1, 0, 1, 0); // repeating timer, fires every 1s
135     epoll.register(timer);
136 
137     // TODO: epoll.eventLoop();
138     // (The timer event may fire but will not be handled until the event loop is
139     // running.)
140 }
141 
142 
143 /*******************************************************************************
144 
145     ITimerEvent base class with abstract handle method.
146 
147 *******************************************************************************/
148 
149 abstract class ITimerEvent : ISelectClient, ISelectable
150 {
151     /***************************************************************************
152 
153         Convenience and compatibility alias.
154 
155     ***************************************************************************/
156 
157     public alias TimerFD.TimerException TimerException;
158 
159     /***************************************************************************
160 
161         Integer file descriptor provided by the operating system and used to
162         manage the custom event.
163 
164     ***************************************************************************/
165 
166     private TimerFD fd;
167 
168     /***********************************************************************
169 
170         Constructor. Creates a file descriptor to manage the event.
171 
172         Params:
173             realtime = true:  use a settable system-wide clock.
174                        false: use a non-settable clock that is not affected by
175                        discontinuous changes in the system clock (e.g., manual
176                        changes to system time).
177 
178     ***********************************************************************/
179 
180     protected this ( bool realtime = false )
181     {
182         this.fd = new TimerFD(realtime);
183     }
184 
185     /***************************************************************************
186 
187         Returns:
188             the epoll events to register for.
189 
190     ***************************************************************************/
191 
192     public override Event events ( )
193     {
194         return Event.EPOLLIN;
195     }
196 
197     /***************************************************************************
198 
199         Returns:
200             the value of the TimerFD's absolute flag (true = absolute timer,
201             false = relative timer)
202 
203     ***************************************************************************/
204 
205     public bool absolute ( )
206     {
207         return this.fd.absolute;
208     }
209 
210     /***************************************************************************
211 
212         Sets the timer to absolute or relative mode.
213 
214         Params:
215             abs = the value of the TimerFD's absolute flag (true = absolute
216                 timer, false = relative timer)
217 
218     ***************************************************************************/
219 
220     public void absolute ( bool abs )
221     {
222         this.fd.absolute = abs;
223     }
224 
225     /***************************************************************************
226 
227         Timer expiration event handler.
228 
229         Params:
230             n =  number of  expirations that have occurred
231 
232         Returns:
233             true to stay registered in epoll or false to unregister.
234 
235     ***************************************************************************/
236 
237     abstract protected bool handle_ ( ulong n );
238 
239     /***********************************************************************
240 
241         Returns the next expiration time.
242 
243         Returns:
244             itimerspec instance containing the next expiration time.
245             - it_value: the amount of time until the timer will next expire. If
246                  both fields are zero, then the timer is currently disarmed.
247                  Contains always a relative value.
248             - it_interval: the interval of the timer. If both fields are zero,
249                  then the timer is set to expire just once, at the time
250                  specified by it_value.
251 
252      ***********************************************************************/
253 
254     public itimerspec time ( )
255     {
256         return this.fd.time;
257     }
258 
259     /***************************************************************************
260 
261         Sets next expiration time of interval timer.
262 
263         Params:
264             first =    Specifies the initial expiration of the timer. Setting
265                        either field to a non-zero value arms the timer. Setting
266                        both fields to zero disarms the timer.
267 
268             interval = Setting one or both fields to non-zero values specifies
269                        the period for repeated timer expirations after the
270                        initial expiration. If both fields are zero, the timer
271                        expires just once, at the time specified by it_value.
272 
273         Returns:
274             the previous expiration time as time().
275 
276      **************************************************************************/
277 
278     public itimerspec set ( timespec first, timespec interval = timespec.init )
279     {
280         return this.fd.set(first, interval);
281     }
282 
283     /***************************************************************************
284 
285         Sets next expiration time of interval timer.
286 
287         Setting first_s or first_ms to a non-zero value arms the timer. Setting
288         both to zero disarms the timer.
289         If both interval_s and interval_,s are zero, the timer expires just
290         once, at the time specified by first_s and first_ms.
291 
292 
293         Params:
294             first_s     = Specifies the number of seconds of the initial
295                           expiration of the timer.
296 
297             first_ms    = Specifies an amount of milliseconds that will be added
298                           to first_s.
299 
300             interval_s = Specifies the number of seconds of the period for
301                           repeated timer expirations after the initial
302                           expiration.
303 
304             interval_ms = Specifies an amount of milliseconds that will be added
305                           to interval_ms.
306 
307         Returns:
308             the previous expiration time as time().
309 
310      **************************************************************************/
311 
312     public itimerspec set ( time_t first_s,        uint first_ms,
313                             time_t interval_s = 0, uint interval_ms = 0 )
314     {
315         return this.fd.set(first_s, first_ms, interval_s, interval_ms);
316     }
317 
318     /***************************************************************************
319 
320         Resets/disarms the timer.
321 
322         Returns:
323             Returns the previous expiration time as time().
324 
325      **************************************************************************/
326 
327     public itimerspec reset ( )
328     {
329         return this.fd.reset();
330     }
331 
332     /***********************************************************************
333 
334         Required by ISelectable interface.
335 
336         Returns:
337             file descriptor used to manage custom event
338 
339     ***********************************************************************/
340 
341     public override Handle fileHandle ( )
342     {
343         return this.fd.fileHandle;
344     }
345 
346     /***************************************************************************
347 
348         Event handler, invoked by the epoll select dispatcher.
349 
350         Params:
351             event = event(s) reported by epoll
352 
353         Returns:
354             true to stay registered in epoll or false to unregister.
355 
356     ***************************************************************************/
357 
358     final override bool handle ( Event event )
359     {
360         return this.handle_(this.fd.handle());
361     }
362 
363     /***************************************************************************
364 
365         Returns an identifier string for this instance
366 
367         Returns:
368             identifier string for this instance
369 
370     ***************************************************************************/
371 
372     debug
373     {
374         import ocean.core.Array : copy;
375         import ocean.text.convert.Formatter;
376 
377         private mstring time_buffer;
378 
379         public override cstring id ( )
380         {
381             this.time_buffer.copy(super.id());
382             auto time = this.time();
383 
384             sformat(this.time_buffer, ": {}s {}ns",
385                 time.it_value.tv_sec, time.it_value.tv_nsec);
386             return this.time_buffer;
387         }
388     }
389 }
390 
391 
392 version (unittest)
393 {
394     import ocean.core.Test;
395     import core.sys.posix.time : CLOCK_MONOTONIC;
396 
397     extern ( C )
398     {
399         alias int clockid_t;
400         int clock_gettime(clockid_t, timespec*);
401     }
402 
403     class TestTimerEvent : ITimerEvent
404     {
405         override protected bool handle_ ( ulong n )
406         {
407             assert(false);
408         }
409     }
410 }
411 
412 
413 /*******************************************************************************
414 
415     Test for setting absolute timers
416 
417 *******************************************************************************/
418 
419 unittest
420 {
421     timespec now;
422     clock_gettime(CLOCK_MONOTONIC, &now);
423 
424     auto timer = new TestTimerEvent;
425     timer.absolute = true;
426 
427     auto set_time = now;
428     set_time.tv_sec += 10;
429     timer.set(set_time);
430 
431     auto get_time = timer.time();
432     ulong nsec = (get_time.it_value.tv_sec * 1_000_000_000)
433         + get_time.it_value.tv_nsec;
434 
435     test!("<=")(nsec, 10_000_000_000);
436 }