timerfd.ha (4054B)
1 // SPDX-License-Identifier: MPL-2.0 2 // (c) Hare authors <https://harelang.org> 3 4 use endian; 5 use errors; 6 use io; 7 use rt; 8 use time; 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 // Flags to use in [[new]]. CLOEXEC is enabled by default, use NOCLOEXEC to 29 // disable it. 30 export type new_flag = enum int { 31 NONE = 0, 32 NONBLOCK = rt::O_NONBLOCK, 33 NOCLOEXEC = rt::O_CLOEXEC, 34 }; 35 36 // Flags to use in [[set]]. 37 export type set_flag = enum int { 38 NONE = 0, 39 ABSTIME = 1, 40 CANCEL_ON_SET = 2, 41 }; 42 43 // Creates a new timerfd. The timer is initially configured without an 44 // expiration; see [[set]] to configure it. 45 export fn new( 46 clockid: time::clock, 47 flags: new_flag, 48 ) (io::file | errors::error) = { 49 flags ^= new_flag::NOCLOEXEC; 50 51 match (rt::timerfd_create(clockid, flags)) { 52 case let fd: int => 53 return fd; 54 case let err: rt::errno => 55 return errors::errno(err); 56 }; 57 }; 58 59 // Sets the expiration configuration for a timerfd, overwriting any 60 // previously set expiration. 61 export fn set( 62 t: io::file, 63 exp: expiration, 64 flags: set_flag, 65 ) (void | errors::error) = { 66 const timerspec = match (exp) { 67 case let o: oneshot => 68 yield rt::itimerspec { 69 it_interval = rt::timespec { tv_sec = 0, tv_nsec = 0 }, 70 it_value = time::duration_to_timespec(o), 71 }; 72 case let i: interval => 73 const interval_timespec = time::duration_to_timespec(i); 74 yield rt::itimerspec { 75 it_interval = interval_timespec, 76 it_value = interval_timespec, 77 }; 78 case let id: interval_delayed => 79 yield rt::itimerspec { 80 it_interval = time::duration_to_timespec(id.0), 81 it_value = time::duration_to_timespec(id.1), 82 }; 83 }; 84 85 match (rt::timerfd_settime(t, flags, &timerspec, null)) { 86 case let ok: int => 87 return; 88 case let err: rt::errno => 89 return errors::errno(err); 90 }; 91 }; 92 93 // Unsets any expiration that was previously set on the given timer. 94 export fn unset( 95 t: io::file, 96 ) (void | errors::error) = { 97 match (rt::timerfd_settime(t, 0, &empty_timerspec, null)) { 98 case int => 99 return; 100 case let err: rt::errno => 101 return errors::errno(err); 102 }; 103 }; 104 105 // Reading from the timerfd returns the number of times the timer has expired 106 // since the last call to [[set]] or [[read]]. This call can be blocking or not 107 // depending on the flags passed to [[new]]. Reading from a blocking unset 108 // timerfd will block forever. 109 export fn read( 110 t: io::file 111 ) (u64 | errors::error) = { 112 let expirations: [8]u8 = [0...]; 113 match (rt::read(t, &expirations, len(expirations))) { 114 case let err: rt::errno => 115 return errors::errno(err); 116 case let z: size => 117 assert(z == len(expirations)); 118 }; 119 return endian::host.getu64(expirations); 120 }; 121 122 @test fn timerfd() void = { 123 let blocking_fd = new(time::clock::MONOTONIC, new_flag::NONE)!; 124 125 // one-shot blocking 126 // the first read should block and eventually return 1 127 // subsequent reads will block indefinitely 128 set(blocking_fd, 100: oneshot, set_flag::NONE)!; 129 let one = read(blocking_fd)!; 130 assert(one == 1); 131 132 // interval blocking 133 // the first read should block and eventually return the number of times 134 // the timer expired 135 // subsequent reads should return instantly with the number of times the 136 // timer expired since the last read 137 set(blocking_fd, 100: interval, set_flag::NONE)!; 138 let first = read(blocking_fd)!; 139 let second = read(blocking_fd)!; 140 141 assert(first > 0); 142 assert(second > 0); 143 144 // unset blocking timer 145 // reading here would block us forever 146 unset(blocking_fd)!; 147 148 io::close(blocking_fd)!; 149 };