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 }