summaryrefslogtreecommitdiff
path: root/ext/sleepy_penguin/eventfd.c
blob: 4f95b0deb81b0de8d8eed24a984de55c8467d16e (plain)
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
172
173
174
#ifdef HAVE_SYS_EVENTFD_H
#include "sleepy_penguin.h"
#include <sys/eventfd.h>

/*
 * call-seq:
 *	EventFD.new(initial_value [, flags])	-> EventFD IO object
 *
 * Creates an EventFD object.  +initial_value+ is a non-negative Integer
 * to start the internal counter at.
 *
 * Starting with Linux 2.6.27, +flags+ may be a mask that consists of any
 * 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
 *
 * Since Linux 2.6.30, +flags+ may also include:
 * - :SEMAPHORE - provides semaphore-like semantics (see EventFD#value)
 */
static VALUE s_new(int argc, VALUE *argv, VALUE klass)
{
	VALUE _initval, _flags, rv;
	unsigned initval;
	int flags;
	int fd;

	rb_scan_args(argc, argv, "11", &_initval, &_flags);
	initval = NUM2UINT(_initval);
	flags = rb_sp_get_flags(klass, _flags, RB_SP_CLOEXEC(EFD_CLOEXEC));

	fd = eventfd(initval, flags);
	if (fd < 0) {
		if (errno == EMFILE || errno == ENFILE || errno == ENOMEM) {
			rb_gc();
			fd = eventfd(initval, flags);
		}
		if (fd < 0)
			rb_sys_fail("eventfd");
	}

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

struct efd_args {
	int fd;
	uint64_t val;
};

static VALUE efd_write(void *_args)
{
	struct efd_args *args = _args;
	ssize_t w = write(args->fd, &args->val, sizeof(uint64_t));

	return (VALUE)w;
}

static VALUE efd_read(void *_args)
{
	struct efd_args *args = _args;
	ssize_t r = read(args->fd, &args->val, sizeof(uint64_t));

	return (VALUE)r;
}

/*
 * call-seq:
 *	efd.incr(integer_value[, nonblock ])	-> true or nil
 *
 * Increments the internal counter by +integer_value+ which is an unsigned
 * Integer value.
 *
 * If +nonblock+ is specified and true, this will return +nil+ if the
 * internal counter will overflow the value of EventFD::MAX.
 * Otherwise it will block until the counter may be incremented without
 * overflowing.
 */
static VALUE incr(int argc, VALUE *argv, VALUE self)
{
	struct efd_args x;
	ssize_t w;
	VALUE value, nonblock;

	rb_scan_args(argc, argv, "11", &value, &nonblock);
	x.fd = rb_sp_fileno(self);
	RTEST(nonblock) ? rb_sp_set_nonblock(x.fd) : blocking_io_prepare(x.fd);
	x.val = (uint64_t)NUM2ULL(value);
retry:
	w = (ssize_t)rb_sp_fd_region(efd_write, &x, x.fd);
	if (w < 0) {
		if (errno == EAGAIN && RTEST(nonblock))
			return Qfalse;
		if (rb_sp_wait(rb_io_wait_writable, self, &x.fd))
			goto retry;
		rb_sys_fail("write(eventfd)");
	}

	return Qtrue;
}

/*
 * call-seq:
 *	efd.value([nonblock])	-> Integer or nil
 *
 * If not created as a semaphore, returns the current value and resets
 * the counter to zero.
 *
 * If created as a semaphore, this decrements the counter value by one
 * and returns +1+.
 *
 * If the counter is zero at the time of the call, this will block until
 * the counter becomes non-zero unless +nonblock+ is +true+, in which
 * case it returns +nil+.
 */
static VALUE getvalue(int argc, VALUE *argv, VALUE self)
{
	struct efd_args x;
	ssize_t w;
	VALUE nonblock;

	rb_scan_args(argc, argv, "01", &nonblock);
	x.fd = rb_sp_fileno(self);
	RTEST(nonblock) ? rb_sp_set_nonblock(x.fd) : blocking_io_prepare(x.fd);
retry:
	w = (ssize_t)rb_sp_fd_region(efd_read, &x, x.fd);
	if (w < 0) {
		if (errno == EAGAIN && RTEST(nonblock))
			return Qnil;
		if (rb_sp_wait(rb_io_wait_readable, self, &x.fd))
			goto retry;
		rb_sys_fail("read(eventfd)");
	}

	return ULL2NUM(x.val);
}

void sleepy_penguin_init_eventfd(void)
{
	VALUE mSleepyPenguin, cEventFD;

	mSleepyPenguin = rb_define_module("SleepyPenguin");

	/*
	 * Document-class: SleepyPenguin::EventFD
	 *
	 * Applications may use EventFD instead of a pipe in cases where
	 * a pipe is only used to signal events.  The kernel overhead for
	 * an EventFD descriptor is much lower than that of a pipe.
	 *
	 * As of Linux 2.6.30, an EventFD may also be used as a semaphore.
	 */
	cEventFD = rb_define_class_under(mSleepyPenguin, "EventFD", rb_cIO);
	rb_define_singleton_method(cEventFD, "new", s_new, -1);

	/*
	 * the maximum value that may be stored in an EventFD,
	 * currently 0xfffffffffffffffe
	 */
	rb_define_const(cEventFD, "MAX", ULL2NUM(0xfffffffffffffffeULL));

#ifdef EFD_NONBLOCK
	NODOC_CONST(cEventFD, "NONBLOCK", INT2NUM(EFD_NONBLOCK));
#endif
#ifdef EFD_CLOEXEC
	NODOC_CONST(cEventFD, "CLOEXEC", INT2NUM(EFD_CLOEXEC));
#endif
#ifdef EFD_SEMAPHORE
	NODOC_CONST(cEventFD, "SEMAPHORE", INT2NUM(EFD_SEMAPHORE));
#endif
	rb_define_method(cEventFD, "value", getvalue, -1);
	rb_define_method(cEventFD, "incr", incr, -1);
}
#endif /* HAVE_SYS_EVENTFD_H */