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 }