1 #include "sleepy_penguin.h"
2 #ifdef HAVE_SYS_EPOLL_H
5 #include "missing_clock_gettime.h"
6 #include "missing_epoll.h"
7 #include "missing_rb_thread_fd_close.h"
8 #include "missing_rb_update_max_fd.h"
13 static uint64_t now_ms(void)
17 clock_gettime(CLOCK_MONOTONIC
, &now
);
19 return now
.tv_sec
* 1000 + (now
.tv_nsec
+ 500000) / 1000000;
22 static void pack_event_data(struct epoll_event
*event
, VALUE obj
)
24 event
->data
.ptr
= (void *)obj
;
27 static VALUE
unpack_event_data(struct epoll_event
*event
)
29 return (VALUE
)event
->data
.ptr
;
32 struct ep_per_thread
{
38 struct epoll_event events
[FLEX_ARRAY
];
41 /* this will raise if the IO is closed */
42 static int ep_fd_check(struct ep_per_thread
*ept
)
44 int save_errno
= errno
;
46 ept
->fd
= rb_sp_fileno(ept
->io
);
52 static struct ep_per_thread
*ept_get(int maxevents
)
54 struct ep_per_thread
*ept
;
57 /* error check here to prevent OOM from posix_memalign */
60 rb_sys_fail("epoll_wait maxevents <= 0");
63 size
= sizeof(struct ep_per_thread
) +
64 sizeof(struct epoll_event
) * maxevents
;
66 ept
= rb_sp_gettlsbuf(&size
);
67 ept
->capa
= maxevents
;
68 ept
->maxevents
= maxevents
;
75 * SleepyPenguin::Epoll::IO.new(flags) -> Epoll::IO object
77 * Creates a new Epoll::IO object with the given +flags+ argument.
78 * +flags+ may currently be +CLOEXEC+ or +0+.
80 static VALUE
s_new(VALUE klass
, VALUE _flags
)
82 int default_flags
= RB_SP_CLOEXEC(EPOLL_CLOEXEC
);
83 int flags
= rb_sp_get_flags(cEpoll
, _flags
, default_flags
);
84 int fd
= epoll_create1(flags
);
88 if (rb_sp_gc_for_fd(errno
))
89 fd
= epoll_create1(flags
);
91 rb_sys_fail("epoll_create1");
95 return rb_call_super(1, &rv
);
100 * epoll_io.epoll_ctl(op, io, events) -> nil
102 * Register, modify, or register a watch for a given +io+ for events.
104 * +op+ may be one of +EPOLL_CTL_ADD+, +EPOLL_CTL_MOD+, or +EPOLL_CTL_DEL+
105 * +io+ is an +IO+ object or one which proxies via the +to_io+ method.
106 * +events+ is an integer mask of events to watch for.
108 * Returns nil on success.
110 static VALUE
epctl(VALUE self
, VALUE _op
, VALUE io
, VALUE events
)
112 struct epoll_event event
;
113 int epfd
= rb_sp_fileno(self
);
114 int fd
= rb_sp_fileno(io
);
115 int op
= NUM2INT(_op
);
118 event
.events
= NUM2UINT(events
);
119 pack_event_data(&event
, io
);
121 rv
= epoll_ctl(epfd
, op
, fd
, &event
);
123 rb_sys_fail("epoll_ctl");
128 static VALUE
epwait_result(struct ep_per_thread
*ept
, int n
)
131 struct epoll_event
*epoll_event
= ept
->events
;
132 VALUE obj_events
, obj
;
138 rb_sys_fail("epoll_wait");
141 for (i
= n
; --i
>= 0; epoll_event
++) {
142 obj_events
= UINT2NUM(epoll_event
->events
);
143 obj
= unpack_event_data(epoll_event
);
144 rb_yield_values(2, obj_events
, obj
);
150 static int epoll_resume_p(uint64_t expire_at
, struct ep_per_thread
*ept
)
154 ep_fd_check(ept
); /* may raise IOError */
158 if (ept
->timeout
< 0)
161 ept
->timeout
= now
> expire_at
? 0 : (int)(expire_at
- now
);
165 static VALUE
nogvl_wait(void *args
)
167 struct ep_per_thread
*ept
= args
;
168 int n
= epoll_wait(ept
->fd
, ept
->events
, ept
->maxevents
, ept
->timeout
);
173 static VALUE
real_epwait(VALUE p
)
176 struct ep_per_thread
*ept
= (struct ep_per_thread
*)p
;
177 uint64_t expire_at
= ept
->timeout
> 0 ? now_ms() + ept
->timeout
: 0;
179 ept
->fd
= rb_sp_fileno(ept
->io
);
181 n
= (long)rb_sp_fd_region(nogvl_wait
, ept
, ept
->fd
);
182 } while (n
< 0 && epoll_resume_p(expire_at
, ept
));
184 return epwait_result(ept
, (int)n
);
189 * ep_io.epoll_wait([maxevents[, timeout]]) { |events, io| ... }
191 * Calls epoll_wait(2) and yields Integer +events+ and +IO+ objects watched
192 * for. +maxevents+ is the maximum number of events to process at once,
193 * lower numbers may prevent starvation when used by epoll_wait in multiple
194 * threads. Larger +maxevents+ reduces syscall overhead for
195 * single-threaded applications. +maxevents+ defaults to 64 events.
196 * +timeout+ is specified in milliseconds, +nil+
197 * (the default) meaning it will block and wait indefinitely.
199 static VALUE
epwait(int argc
, VALUE
*argv
, VALUE self
)
201 VALUE timeout
, maxevents
;
202 struct ep_per_thread
*ept
;
206 rb_scan_args(argc
, argv
, "02", &maxevents
, &timeout
);
207 t
= NIL_P(timeout
) ? -1 : NUM2INT(timeout
);
209 ept
= ept_get(NIL_P(maxevents
) ? 64 : NUM2INT(maxevents
));
213 return rb_ensure(real_epwait
, (VALUE
)ept
, rb_sp_puttlsbuf
, (VALUE
)ept
);
217 static VALUE
event_flags(VALUE self
, VALUE flags
)
219 return UINT2NUM(rb_sp_get_uflags(self
, flags
));
222 void sleepy_penguin_init_epoll(void)
224 VALUE mSleepyPenguin
, cEpoll_IO
;
227 * Document-module: SleepyPenguin
229 * require "sleepy_penguin"
230 * include SleepyPenguin
232 * The SleepyPenguin namespace includes the Epoll, Inotify,
233 * TimerFD, EventFD classes in its top level and no other constants.
235 * If you are uncomfortable including SleepyPenguin, you may also
236 * use the "SP" alias if it doesn't conflict with existing code:
238 * require "sleepy_penguin/sp"
240 * And then access classes via:
248 mSleepyPenguin
= rb_define_module("SleepyPenguin");
251 * Document-class: SleepyPenguin::Epoll
253 * The Epoll class provides high-level access to epoll(7)
254 * functionality in the Linux 2.6 and later kernels. It provides
255 * fork and GC-safety for Ruby objects stored within the +IO+ object
256 * and may be passed as an argument to IO.select.
258 cEpoll
= rb_define_class_under(mSleepyPenguin
, "Epoll", rb_cObject
);
261 * Document-class: SleepyPenguin::Epoll::IO
263 * Epoll::IO is a low-level class. It does not provide fork nor
264 * GC-safety, so Ruby +IO+ objects added via epoll_ctl must be retained
265 * by the application until IO#close is called.
267 cEpoll_IO
= rb_define_class_under(cEpoll
, "IO", rb_cIO
);
268 rb_define_singleton_method(cEpoll_IO
, "new", s_new
, 1);
270 rb_define_method(cEpoll_IO
, "epoll_ctl", epctl
, 3);
271 rb_define_method(cEpoll_IO
, "epoll_wait", epwait
, -1);
273 rb_define_method(cEpoll
, "__event_flags", event_flags
, 1);
275 /* registers a target +IO+ object via epoll_ctl */
276 rb_define_const(cEpoll
, "CTL_ADD", INT2NUM(EPOLL_CTL_ADD
));
278 /* unregisters a target +IO+ object via epoll_ctl */
279 rb_define_const(cEpoll
, "CTL_DEL", INT2NUM(EPOLL_CTL_DEL
));
281 /* modifies the registration of a target +IO+ object via epoll_ctl */
282 rb_define_const(cEpoll
, "CTL_MOD", INT2NUM(EPOLL_CTL_MOD
));
284 /* specifies whether close-on-exec flag is set for Epoll.new */
285 rb_define_const(cEpoll
, "CLOEXEC", INT2NUM(EPOLL_CLOEXEC
));
287 /* watch for read/recv operations */
288 rb_define_const(cEpoll
, "IN", UINT2NUM(EPOLLIN
));
290 /* watch for write/send operations */
291 rb_define_const(cEpoll
, "OUT", UINT2NUM(EPOLLOUT
));
295 * Watch a specified io for shutdown(SHUT_WR) on the remote-end.
296 * Available since Linux 2.6.17.
298 rb_define_const(cEpoll
, "RDHUP", UINT2NUM(EPOLLRDHUP
));
303 * This prevents system suspend while event is ready.
304 * This requires the caller to have the CAP_BLOCK_SUSPEND capability
305 * Available since Linux 3.5
307 rb_define_const(cEpoll
, "WAKEUP", UINT2NUM(EPOLLWAKEUP
));
310 #ifdef EPOLLEXCLUSIVE
312 * Sets an exclusive wakeup mode for the epoll object
313 * that is being attached to the target +IO+. This
314 * avoids thundering herd scenarios when the same
315 * target +IO+ is shared among multiple epoll objects.
316 * Available since Linux 4.5
318 rb_define_const(cEpoll
, "EXCLUSIVE", UINT2NUM(EPOLLEXCLUSIVE
));
321 /* watch for urgent read(2) data */
322 rb_define_const(cEpoll
, "PRI", UINT2NUM(EPOLLPRI
));
325 * watch for errors, there is no need to specify this,
326 * it is always monitored when an +IO+ is watched
328 rb_define_const(cEpoll
, "ERR", UINT2NUM(EPOLLERR
));
331 * watch for hangups, there is no need to specify this,
332 * it is always monitored when an +IO+ is watched
334 rb_define_const(cEpoll
, "HUP", UINT2NUM(EPOLLHUP
));
336 /* notifications are only Edge Triggered, see epoll(7) */
337 rb_define_const(cEpoll
, "ET", UINT2NUM((uint32_t)EPOLLET
));
339 /* unwatch the descriptor once any event has fired */
340 rb_define_const(cEpoll
, "ONESHOT", UINT2NUM(EPOLLONESHOT
));
342 id_for_fd
= rb_intern("for_fd");
345 * the high-level interface is implemented in Ruby,
346 * see lib/sleepy_penguin/epoll.rb
349 #endif /* HAVE_SYS_EPOLL_H */