hare

[hare] The Hare programming language
git clone https://git.torresjrjr.com/hare.git
Log | Files | Refs | README | LICENSE

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 };