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 }