hare

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

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