1 /*******************************************************************************
2 
3     Support for app-level timers.
4 
5     Copyright:
6         Copyright (c) 2018 dunnhumby Germany GmbH.
7         All rights reserved.
8 
9     License:
10         Boost Software License Version 1.0. See LICENSE_BOOST.txt for details.
11         Alternatively, this file may be distributed under the terms of the Tango
12         3-Clause BSD License (see LICENSE_BSD.txt for details).
13 
14 *******************************************************************************/
15 
16 module ocean.application.components.Timers;
17 
18 /// ditto
19 public class Timers
20 {
21     import ocean.core.Verify;
22     import ocean.io.select.EpollSelectDispatcher;
23     import ocean.io.select.client.TimerSet;
24     import ocean.task.Scheduler;
25     import ocean.time.MicrosecondsClock;
26     import ocean.time.timeout.TimeoutManager;
27     import BucketElementFreeList = ocean.util.container.map.model.BucketElementFreeList;
28     import ocean.util.log.Logger;
29 
30     /// Static logger.
31     static private Logger log;
32 
33     /***************************************************************************
34 
35         Static constructor.
36 
37     ***************************************************************************/
38 
39     static this ( )
40     {
41         log = Log.lookup("ocean.application.components.Timers");
42     }
43 
44     /// Type of delegate called when an event fires. The delegate's return value
45     /// indicates whether the timed event should remain registered (true) or be
46     /// unregistered (false).
47     public alias bool delegate ( ) EventDg;
48 
49     /// Data stored for each event registered with the internal timer set (see
50     /// below).
51     private struct EventData
52     {
53 
54         /// Delegate to call when event fires.
55         public EventDg dg;
56 
57         /// Period after which event should fire again. (Note that we need to
58         /// store this because timer set events are one-off, unlike
59         /// TimerEvents.)
60         public ulong repeat_microsec;
61 
62         /// The expected time of the next call for the timer.
63         public ulong next_call;
64     }
65 
66     /***************************************************************************
67 
68         Internal timer set used to track the set of timed events. A timer set
69         is used to avoid the need for managing a set of TimerEvents, one per
70         registered event.
71 
72     ***************************************************************************/
73 
74     private TimerSet!(EventData) timer_set;
75 
76     /***************************************************************************
77 
78         Constructor. Creates the internal event timer set.
79 
80     ***************************************************************************/
81 
82     public this ( )
83     {
84         this(theScheduler.epoll);
85     }
86 
87     /***************************************************************************
88 
89         Constructor. Creates the internal event timer set.
90 
91         Params:
92             epoll = select dispatcher with which to register the timer set
93 
94     ***************************************************************************/
95 
96     public this ( EpollSelectDispatcher epoll )
97     {
98         this.timer_set = new TimerSet!(EventData)(epoll, 0,
99             BucketElementFreeList.instantiateAllocator!(TimeoutManagerBase.ExpiryToClient));
100     }
101 
102     /***************************************************************************
103 
104         Registers a timer with the extension. The provided delegate will be
105         called repeatedly according to the specified period, as long as it
106         returns true.
107 
108         Params:
109             dg = delegate to call periodically
110             period_s = seconds between calls of the delegate
111 
112     ***************************************************************************/
113 
114     public void register ( scope EventDg dg, double period_s )
115     {
116         verify(dg !is null);
117         verify(period_s >= 0.0);
118         this.registerMicrosec(dg, secToMicrosec(period_s), secToMicrosec(period_s));
119     }
120 
121     /***************************************************************************
122 
123         Registers a timer with the extension. The provided delegate will be
124         called once after the initial delay specified, then repeatedly according
125         to the specified period, as long as it returns true.
126 
127         Params:
128             dg = delegate to call periodically
129             init_s = seconds before initial call of the delegate
130             period_s = seconds between subsequent calls of the delegate
131 
132     ***************************************************************************/
133 
134     public void register ( scope EventDg dg, double init_s, double period_s )
135     {
136         verify(dg !is null);
137         verify(init_s >= 0.0);
138         verify(period_s >= 0.0);
139 
140         this.registerMicrosec(dg, secToMicrosec(init_s), secToMicrosec(period_s));
141     }
142 
143     /***************************************************************************
144 
145         Registers a timer with the extension. The provided delegate will be
146         called once after the initial delay specified, then repeatedly according
147         to the specified period, as long as it returns true.
148 
149         Note that this internal method is called both from the public register()
150         methods and the private eventFired().
151 
152         Params:
153             dg = delegate to call periodically
154             init_microsec = microseconds before initial call of the delegate
155             period_microsec = microseconds between subsequent calls of the
156                 delegate
157 
158     ***************************************************************************/
159 
160     public void registerMicrosec ( scope EventDg dg, ulong init_microsec, ulong period_microsec )
161     {
162         verify(dg !is null);
163 
164         this.timer_set.schedule(
165             ( ref EventData event )
166             {
167                 event.dg = dg;
168                 event.repeat_microsec = period_microsec;
169                 event.next_call = MicrosecondsClock.now_us() + init_microsec;
170             },
171             &this.eventFired, init_microsec);
172     }
173 
174     /***************************************************************************
175 
176         Unregisters all timed events (thus unregisters the internal TimerEvent
177         from epoll).
178 
179     ***************************************************************************/
180 
181     public void clear ( )
182     {
183         this.timer_set.clear();
184     }
185 
186     /***************************************************************************
187 
188         Internal delegate called when a scheduled event fires. Calls the user's
189         delegate and re-schedules the event after the specified period. If the
190         time spent in user's delegate is longer than the interval then delegate
191         calls for those intervals will be skipped.
192 
193         Params:
194             event = data attached to the event which fired
195 
196     ***************************************************************************/
197 
198     private void eventFired ( ref EventData event )
199     {
200         bool reregister = true; // by default, always stay registered
201 
202         try
203         {
204             reregister = event.dg();
205         }
206         catch (Exception e)
207         {
208             try
209             {
210                 log.error("Unhnandled exception in TimerExt's callback: {}",
211                         e.message());
212             }
213             catch (Exception)
214             {
215                 // ignore the potential logger failure, keep the timerset running
216             }
217         }
218 
219         if ( reregister )
220         {
221             do
222             {
223                 event.next_call += event.repeat_microsec;
224             }
225             while ( event.next_call < MicrosecondsClock.now_us() );
226 
227             auto ms_till = event.next_call - MicrosecondsClock.now_us();
228             this.registerMicrosec(event.dg, ms_till, event.repeat_microsec);
229         }
230     }
231 
232     /***************************************************************************
233 
234         Converts the provided floating point time in seconds to an integer time
235         in microseconds.
236 
237         Params:
238             time_s = floating point time in seconds
239 
240         Returns:
241             corresponding integer time in microseconds
242 
243     ***************************************************************************/
244 
245     private static ulong secToMicrosec ( double time_s )
246     {
247         return cast(ulong)(time_s * 1_000_000);
248     }
249 }