timerfd.ha (3796B)
1 // License: MPL-2.0 2 // (c) 2021 Vincent Dagonneau <v@vda.io> 3 4 use errors; 5 use rt; 6 use time; 7 use io; 8 use endian; 9 10 // The timer will trigger only once, after the set duration and never after. 11 export type oneshot = time::duration; 12 13 // The timer will trigger once after a configured delay, then periodically at 14 // the given interval. 15 export type interval_delayed = (time::duration, time::duration); 16 17 // The timer will trigger periodically at the given interval. 18 export type interval = time::duration; 19 20 // The expiration configuration for the timer. 21 export type expiration = (oneshot | interval | interval_delayed); 22 23 const empty_timerspec: rt::itimerspec = rt::itimerspec { 24 it_interval = rt::timespec { tv_sec = 0, tv_nsec = 0 }, 25 it_value = rt::timespec { tv_sec = 0, tv_nsec = 0 }, 26 }; 27 28 // Creates a new timerfd. The timer is initially configured without an 29 // expiration; see [[set]] to configure it. 30 export fn new( 31 clockid: time::clock, 32 flags: int 33 ) (io::file | errors::error) = { 34 match (rt::timerfd_create(clockid, flags)) { 35 case let fd: int => 36 return fd; 37 case let err: rt::errno => 38 return errors::errno(err); 39 }; 40 }; 41 42 // Sets the expiration configuration for a timerfd, overwriting any 43 // previously set expiration. 44 export fn set( 45 t: io::file, 46 exp: expiration, 47 flags: int 48 ) (void | errors::error) = { 49 let interval_timespec = rt::timespec { ... }; 50 const timerspec = match (exp) { 51 case let o: oneshot => 52 time::duration_to_timespec(o, &interval_timespec); 53 yield rt::itimerspec { 54 it_interval = rt::timespec { tv_sec = 0, tv_nsec = 0 }, 55 it_value = interval_timespec, 56 }; 57 case let i: interval => 58 time::duration_to_timespec(i, &interval_timespec); 59 yield rt::itimerspec { 60 it_interval = interval_timespec, 61 it_value = interval_timespec, 62 }; 63 case let id: interval_delayed => 64 let delay_timespec = rt::timespec { ... }; 65 time::duration_to_timespec(id.0, &interval_timespec); 66 time::duration_to_timespec(id.1, &delay_timespec); 67 yield rt::itimerspec { 68 it_interval = interval_timespec, 69 it_value = delay_timespec, 70 }; 71 }; 72 73 match (rt::timerfd_settime(t, flags, &timerspec, null)) { 74 case let ok: int => 75 return; 76 case let err: rt::errno => 77 return errors::errno(err); 78 }; 79 }; 80 81 // Unsets any expiration that was previously set on the given timer. 82 export fn unset( 83 t: io::file, 84 ) (void | errors::error) = { 85 match (rt::timerfd_settime(t, 0, &empty_timerspec, null)) { 86 case int => 87 return; 88 case let err: rt::errno => 89 return errors::errno(err); 90 }; 91 }; 92 93 // Reading from the timerfd returns the number of time the timer expired. This 94 // call can be blocking or not depending on the flags passed to [[new]]. Reading 95 // from a blocking unset timerfd will block forever. 96 export fn read( 97 t: io::file 98 ) (u64 | errors::error) = { 99 let expirations: [8]u8 = [0...]; 100 match (rt::read(t, &expirations, len(expirations))) { 101 case let err: rt::errno => 102 return errors::errno(err); 103 case let z: size => 104 assert(z == len(expirations)); 105 }; 106 return endian::host.getu64(expirations); 107 }; 108 109 @test fn timerfd() void = { 110 let blocking_fd = new(time::clock::MONOTONIC, 0)!; 111 112 // one-shot blocking 113 // the first read should block and eventually return 1 114 // subsequent reads will block indefinitely 115 set(blocking_fd, 100: oneshot, 0)!; 116 let one = read(blocking_fd)!; 117 assert(one == 1); 118 119 // interval blocking 120 // the first read should block and eventually return the number of time 121 // the timer expired 122 // subsequent reads should return instantly with the number of time the 123 // timer expired since the last read 124 set(blocking_fd, 100: interval, 0)!; 125 let first = read(blocking_fd)!; 126 let second = read(blocking_fd)!; 127 128 assert(first > 0); 129 assert(second > 0); 130 131 // unset blocking timer 132 // reading here would block us forever 133 unset(blocking_fd)!; 134 135 io::close(blocking_fd)!; 136 };