sleepy_penguin.git  about / heads / tags
Linux I/O events for Ruby
blob 7957802585767731df93c9dd1f0322cde2ffbfc5 4626 bytes (raw)
$ git show pu:ext/sleepy_penguin/timerfd.c	# shows this blob on the CLI

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
 
#ifdef HAVE_SYS_TIMERFD_H
#include "sleepy_penguin.h"
#include <sys/timerfd.h>
#include "value2timespec.h"

/*
 * call-seq:
 *	TimerFD.new([clockid[, flags]]) -> TimerFD IO object
 *
 * Creates a new timer as an IO object.
 *
 * If set +clockid+ must be be one of the following:
 * - :REALTIME - use the settable clock
 * - :MONOTONIC - use the non-settable clock unaffected by manual changes
 *
 * +clockid+ defaults to :MONOTONIC if unspecified
 * +flags+ may be any or none of the following:
 *
 * - :CLOEXEC - set the close-on-exec flag on the new object
 * - :NONBLOCK - set the non-blocking I/O flag on the new object
 */
static VALUE s_new(int argc, VALUE *argv, VALUE klass)
{
	VALUE cid, fl, rv;
	int clockid, flags;
	int fd;

	rb_scan_args(argc, argv, "02", &cid, &fl);
	clockid = NIL_P(cid) ? CLOCK_MONOTONIC : rb_sp_get_flags(klass, cid, 0);
	flags = rb_sp_get_flags(klass, fl, RB_SP_CLOEXEC(TFD_CLOEXEC));

	fd = timerfd_create(clockid, flags);
	if (fd < 0) {
		if (rb_sp_gc_for_fd(errno))
			fd = timerfd_create(clockid, flags);
		if (fd < 0)
			rb_sys_fail("timerfd_create");
	}

	rv = INT2FIX(fd);
	return rb_call_super(1, &rv);
}

static VALUE itimerspec2ary(struct itimerspec *its)
{
	VALUE interval = timespec2num(&its->it_interval);
	VALUE value = timespec2num(&its->it_value);

	return rb_ary_new3(2, interval, value);
}

/*
 * call-seq:
 *	tfd.settime(flags, interval, value) -> [ old_interval, old_value ]
 *
 * Arms (starts) or disarms (stops) the timer referred by the TimerFD object
 * and returns the old value of the timer.
 *
 * +flags+ is either zero (or nil) to start a relative timer or :ABSTIME
 * to start an absolute timer.  If the +interval+ is zero, the timer fires
 * only once, otherwise the timer is fired every +interval+ seconds.
 * +value+ is the time of the initial expiration in seconds.
 */
static VALUE settime(VALUE self, VALUE fl, VALUE interval, VALUE value)
{
	int fd = rb_sp_fileno(self);
	int flags = rb_sp_get_flags(self, fl, 0);
	struct itimerspec old, new;

	value2timespec(&new.it_interval, interval);
	value2timespec(&new.it_value, value);

	if (timerfd_settime(fd, flags, &new, &old) < 0)
		rb_sys_fail("timerfd_settime");

	return itimerspec2ary(&old);
}

/*
 * call-seq:
 *	tfd#gettime	-> [ interval, value ]
 *
 * Returns the current +interval+ and +value+ of the timer as an Array.
 */
static VALUE gettime(VALUE self)
{
	int fd = rb_sp_fileno(self);
	struct itimerspec curr;

	if (timerfd_gettime(fd, &curr) < 0)
		rb_sys_fail("timerfd_gettime");

	return itimerspec2ary(&curr);
}

static VALUE tfd_read(void *args)
{
	uint64_t *buf = args;
	int fd = (int)(*buf);
	ssize_t r = read(fd, buf, sizeof(uint64_t));

	return (VALUE)r;
}

/*
 * call-seq:
 *	tfd.expirations([nonblock])		-> Integer
 *
 * Returns the number of expirations that have occurred.  This will block
 * if no expirations have occurred at the time of the call.  Returns +nil+
 * if +nonblock+ is passed and is +true+
 */
static VALUE expirations(int argc, VALUE *argv, VALUE self)
{
	ssize_t r;
	int fd = rb_sp_fileno(self);
	uint64_t buf = (uint64_t)fd;
	VALUE nonblock;

	rb_scan_args(argc, argv, "01", &nonblock);
	if (RTEST(nonblock))
		rb_sp_set_nonblock(fd);
retry:
	r = (ssize_t)rb_sp_fd_region(tfd_read, &buf, fd);
	if (r < 0) {
		if (errno == EAGAIN && RTEST(nonblock))
			return Qnil;
		if (rb_sp_wait(rb_io_wait_readable, self, &fd))
			goto retry;
		rb_sys_fail("read(timerfd)");
	}

	return ULL2NUM(buf);
}

void sleepy_penguin_init_timerfd(void)
{
	VALUE mSleepyPenguin, cTimerFD;

	mSleepyPenguin = rb_define_module("SleepyPenguin");

	/*
	 * Document-class: SleepyPenguin::TimerFD
	 *
	 * TimerFD exposes kernel timers as IO objects that may be monitored
	 * by IO.select or Epoll.  IO#close disarms the timers and returns
	 * resources back to the kernel.
	 */
	cTimerFD = rb_define_class_under(mSleepyPenguin, "TimerFD", rb_cIO);
	rb_define_singleton_method(cTimerFD, "new", s_new, -1);

	NODOC_CONST(cTimerFD, "REALTIME", UINT2NUM(CLOCK_REALTIME));
	NODOC_CONST(cTimerFD, "MONOTONIC", UINT2NUM(CLOCK_MONOTONIC));
	NODOC_CONST(cTimerFD, "ABSTIME", UINT2NUM(TFD_TIMER_ABSTIME));
#ifdef TFD_TIMER_CANCEL_ON_SET
	NODOC_CONST(cTimerFD, "CANCEL_ON_SET",
	            UINT2NUM(TFD_TIMER_CANCEL_ON_SET));
#endif

#ifdef TFD_NONBLOCK
	NODOC_CONST(cTimerFD, "NONBLOCK", UINT2NUM(TFD_NONBLOCK));
#endif
#ifdef TFD_CLOEXEC
	NODOC_CONST(cTimerFD, "CLOEXEC", UINT2NUM(TFD_CLOEXEC));
#endif

	rb_define_method(cTimerFD, "settime", settime, 3);
	rb_define_method(cTimerFD, "gettime", gettime, 0);
	rb_define_method(cTimerFD, "expirations", expirations, -1);
}
#endif /* HAVE_SYS_TIMERFD_H */

git clone https://yhbt.net/sleepy_penguin.git