1 #include "sleepy_penguin.h"
2 #ifdef HAVE_SYS_EVENT_H
8 #include "missing_clock_gettime.h"
9 #include "missing_rb_thread_fd_close.h"
10 #include "missing_rb_update_max_fd.h"
11 #include "value2timespec.h"
13 #ifdef HAVE_SYS_MOUNT_H /* for VQ_* flags on FreeBSD */
14 # include <sys/mount.h>
17 /* not bothering with overflow checking for backwards compat */
19 # define RARRAY_LENINT(ary) (int)RARRAY_LEN(ary)
21 #ifndef RARRAY_CONST_PTR
22 # define RARRAY_CONST_PTR(ary) RARRAY_PTR(ary)
25 # define NUM2SHORT(n) (short)NUM2INT(n)
28 # define NUM2USHORT(n) (short)NUM2UINT(n)
32 * Rubinius does not support RSTRUCT_* in the C API:
33 * ref: https://github.com/rubinius/rubinius/issues/494
36 # define RBX_STRUCT (1)
37 # define RSTRUCT_LEN(s) 0, rb_bug("RSTRUCT_LEN attempted in Rubinius")
38 # define RSTRUCT_PTR(s) NULL, rb_bug("RSTRUCT_PTR attempted in Rubinius")
40 # define RBX_STRUCT (0)
43 static const long NANO_PER_SEC
= 1000000000;
45 static VALUE mEv
, mEvFilt
, mNote
, mVQ
;
47 struct kq_per_thread
{
55 struct kevent events
[FLEX_ARRAY
];
58 static void tssub(struct timespec
*a
, struct timespec
*b
, struct timespec
*res
)
60 res
->tv_sec
= a
->tv_sec
- b
->tv_sec
;
61 res
->tv_nsec
= a
->tv_nsec
- b
->tv_nsec
;
62 if (res
->tv_nsec
< 0) {
64 res
->tv_nsec
+= NANO_PER_SEC
;
68 /* this will raise if the IO is closed */
69 static int kq_fd_check(struct kq_per_thread
*kpt
)
71 int save_errno
= errno
;
73 kpt
->fd
= rb_sp_fileno(kpt
->io
);
79 static struct kq_per_thread
*kpt_get(int nchanges
, int nevents
)
81 struct kq_per_thread
*kpt
;
83 int max
= nchanges
> nevents
? nchanges
: nevents
;
85 /* error check here to prevent OOM from posix_memalign */
88 rb_sys_fail("kevent got negative events < 0");
91 size
= sizeof(struct kq_per_thread
) + sizeof(struct kevent
) * max
;
92 kpt
= rb_sp_gettlsbuf(&size
);
94 kpt
->nchanges
= nchanges
;
95 kpt
->nevents
= nevents
;
102 * SleepyPenguin::Kqueue::IO.new -> Kqueue::IO object
104 * Creates a new Kqueue::IO object. This is a wrapper around the kqueue(2)
105 * system call which creates a Ruby IO object around the kqueue descriptor.
107 * kqueue descriptors are automatically invalidated by the OS across fork,
108 * so care must be taken when forking.
109 * Setting IO#autoclose=false is recommended for applications which fork
110 * after kqueue creation.
112 static VALUE
s_new(VALUE klass
)
120 * ENOMEM/EMFILE/ENFILE are the only documented errors
121 * for kqueue(), hope GC can give us some space to retry:
126 rb_sys_fail("kqueue");
129 flags
= fcntl(fd
, F_GETFD
);
131 fcntl(fd
, F_SETFD
, flags
| FD_CLOEXEC
);
135 return rb_call_super(1, &rv
);
138 static void yield_kevent(struct kevent
*event
)
140 VALUE ident
= ULONG2NUM((unsigned long)event
->ident
); /* uintptr_t */
141 VALUE filter
= INT2NUM((int)event
->filter
); /* short */
142 VALUE flags
= UINT2NUM((unsigned)event
->flags
); /* u_short */
143 VALUE fflags
= UINT2NUM((unsigned)event
->fflags
); /* u_int */
144 VALUE data
= LONG2NUM((long)event
->data
); /* intptr_t */
145 VALUE udata
= (VALUE
)event
->udata
; /* void * */
147 rb_yield_values(6, ident
, filter
, flags
, fflags
, data
, udata
);
150 static VALUE
kevent_result(struct kq_per_thread
*kpt
, int nevents
)
153 struct kevent
*event
= kpt
->events
;
159 rb_sys_fail("kevent");
162 for (i
= nevents
; --i
>= 0; event
++)
165 return INT2NUM(nevents
);
169 * returns true if we were interrupted by a signal and resumable,
170 * updating the timeout timespec with the remaining time if needed.
173 kevent_resume_p(struct timespec
*expire_at
, struct kq_per_thread
*kpt
)
177 kq_fd_check(kpt
); /* may raise IOError */
183 * kevent is not interruptible until changes are sent,
184 * so if we got here, we already got our changes in
188 /* we're waiting forever */
192 clock_gettime(CLOCK_MONOTONIC
, &now
);
193 if (now
.tv_sec
> expire_at
->tv_sec
)
195 if (now
.tv_sec
== expire_at
->tv_sec
&& now
.tv_nsec
> expire_at
->tv_nsec
)
198 tssub(expire_at
, &now
, kpt
->ts
);
202 static VALUE
nogvl_kevent(void *args
)
204 struct kq_per_thread
*kpt
= args
;
205 int nevents
= kevent(kpt
->fd
, kpt
->events
, kpt
->nchanges
,
206 kpt
->events
, kpt
->nevents
, kpt
->ts
);
208 return (VALUE
)nevents
;
211 static void changelist_prepare(struct kevent
*, VALUE
);
213 static VALUE
do_kevent(struct kq_per_thread
*kpt
)
216 struct timespec expire_at
;
219 changelist_prepare(kpt
->events
, kpt
->changelist
);
222 clock_gettime(CLOCK_MONOTONIC
, &expire_at
);
224 expire_at
.tv_sec
+= kpt
->ts
->tv_sec
;
225 expire_at
.tv_nsec
+= kpt
->ts
->tv_nsec
;
226 if (expire_at
.tv_nsec
> NANO_PER_SEC
) {
228 expire_at
.tv_nsec
-= NANO_PER_SEC
;
233 nevents
= (long)rb_sp_fd_region(nogvl_kevent
, kpt
, kpt
->fd
);
234 } while (nevents
< 0 && kevent_resume_p(&expire_at
, kpt
));
236 return kevent_result(kpt
, (int)nevents
);
239 #if defined(HAVE_RB_STRUCT_SIZE) && defined(RSTRUCT_GET)
240 static void ev_set_struct(struct kevent
*ev
, VALUE event
)
242 if (rb_struct_size(event
) == INT2NUM(6)) {
243 uintptr_t ident
= (uintptr_t)NUM2ULONG(RSTRUCT_GET(event
, 0));
244 short filter
= NUM2SHORT(RSTRUCT_GET(event
, 1));
245 unsigned short flags
= NUM2USHORT(RSTRUCT_GET(event
, 2));
246 unsigned fflags
= (unsigned)NUM2UINT(RSTRUCT_GET(event
, 3));
247 intptr_t data
= (intptr_t)NUM2LONG(RSTRUCT_GET(event
, 4));
248 void *udata
= (void *)RSTRUCT_GET(event
, 5);
250 EV_SET(ev
, ident
, filter
, flags
, fflags
, data
, udata
);
252 rb_raise(rb_eTypeError
, "unsupported struct in changelist");
255 #elif RBX_STRUCT == 0 && defined(RSTRUCT_LEN) && defined(RSTRUCT_PTR)
257 static void ev_set_struct(struct kevent
*ev
, VALUE event
)
259 long len
= RSTRUCT_LEN(*event
);
261 const VALUE
*ptr
= RSTRUCT_PTR(*event
);
262 uintptr_t ident
= (uintptr_t)NUM2ULONG(ptr
[0]);
263 short filter
= NUM2SHORT(ptr
[1]);
264 unsigned short flags
= NUM2USHORT(ptr
[2]);
265 unsigned fflags
= (unsigned)NUM2UINT(ptr
[3]);
266 intptr_t data
= (intptr_t)NUM2LONG(ptr
[4]);
267 void *udata
= (void *)ptr
[5];
269 EV_SET(event
, ident
, filter
, flags
, fflags
, data
, udata
);
271 rb_raise(rb_eTypeError
, "unsupported struct in changelist");
275 static void ev_set_struct(struct kevent
*ev
, VALUE event
)
277 rb_raise(rb_eTypeError
, "unsupported struct in changelist");
281 static void ev_set_ary(struct kevent
*ev
, VALUE event
)
283 long len
= RARRAY_LEN(event
);
284 const VALUE
*ptr
= RARRAY_CONST_PTR(event
);
287 uintptr_t ident
= (uintptr_t)NUM2ULONG(ptr
[0]);
288 short filter
= NUM2SHORT(ptr
[1]);
289 unsigned short flags
= NUM2USHORT(ptr
[2]);
290 unsigned fflags
= (unsigned)NUM2UINT(ptr
[3]);
291 intptr_t data
= (intptr_t)NUM2LONG(ptr
[4]);
292 void *udata
= (void *)ptr
[5];
294 EV_SET(ev
, ident
, filter
, flags
, fflags
, data
, udata
);
297 rb_raise(rb_eTypeError
,
298 "changelist must be an array of 6-element arrays or structs");
301 /* sets ptr and len */
302 static void unpack_event(struct kevent
*ev
, VALUE event
)
304 switch (TYPE(event
)) {
307 event
= rb_funcall(event
, rb_intern("to_a"), 0, 0);
308 /* fall-through to T_ARRAY */
310 ev_set_struct(ev
, event
);
314 ev_set_ary(ev
, event
);
316 rb_raise(rb_eTypeError
, "unsupported type in changelist");
320 static void ary2eventlist(struct kevent
*events
, VALUE changelist
)
322 const VALUE
*chg
= RARRAY_CONST_PTR(changelist
);
323 long i
= RARRAY_LEN(changelist
);
325 for (; --i
>= 0; chg
++)
326 unpack_event(events
++, *chg
);
330 * Convert an Ruby representation of the changelist to "struct kevent"
332 static void changelist_prepare(struct kevent
*events
, VALUE changelist
)
334 switch (TYPE(changelist
)) {
336 ary2eventlist(events
, changelist
);
338 case T_STRUCT
: /* single event */
339 unpack_event(events
, changelist
);
342 rb_bug("changelist_prepare not type filtered by sp_kevent");
348 * kq_io.kevent([changelist[, nevents[, timeout]]]) { |ident,filter,flags,fflags,data,udata| ... }
350 * This is a wrapper around the kevent(2) system call to change and/or
351 * retrieve events from the underlying kqueue descriptor.
353 * +changelist+ may be nil, a single Kevent struct or an array of Kevent
354 * structs. If +changelist+ is nil, no changes will be made to the
355 * underlying kqueue object.
357 * +nevents+ may be non-negative integer or nil. If +nevents+ is zero or
358 * nil, no events are retrieved. If +nevents+ is positive, a block must
359 * be passed to kevent for each event.
361 * +timeout+ is the numeric timeout in seconds to wait for +nevents+.
362 * If nil and +nevents+ is positive, kevent will sleep forever.
363 * +timeout+ may be in a floating point number if subsecond resolution
364 * is required. If +nevents+ is nil or zero and +timeout+ is not specified,
365 * +timeout+ is implied to be zero.
367 * If event retrieval is desired, a block taking 6-elements (one for each
368 * field of the kevent struct) must be passed.
370 static VALUE
sp_kevent(int argc
, VALUE
*argv
, VALUE self
)
372 struct timespec ts
, *t
;
373 VALUE changelist
, events
, timeout
;
374 struct kq_per_thread
*kpt
;
375 int nchanges
, nevents
;
377 rb_scan_args(argc
, argv
, "03", &changelist
, &events
, &timeout
);
379 switch (TYPE(changelist
)) {
380 case T_NIL
: nchanges
= 0; break;
381 case T_STRUCT
: nchanges
= 1; break;
382 case T_ARRAY
: nchanges
= RARRAY_LENINT(changelist
); break;
384 rb_raise(rb_eTypeError
, "unhandled type for kevent changelist");
387 if (rb_block_given_p()) {
389 rb_raise(rb_eArgError
,
390 "block given but nevents not specified");
391 nevents
= NUM2INT(events
);
393 rb_raise(rb_eArgError
, "nevents must be non-negative");
396 rb_raise(rb_eArgError
,
397 "nevents specified but block not given");
401 t
= NIL_P(timeout
) ? NULL
: value2timespec(&ts
, timeout
);
402 kpt
= kpt_get(nchanges
, nevents
);
404 kpt
->changelist
= changelist
;
406 kpt
->fd
= rb_sp_fileno(kpt
->io
);
408 return rb_ensure(do_kevent
, (VALUE
)kpt
, rb_sp_puttlsbuf
, (VALUE
)kpt
);
411 /* initialize constants in the SleepyPenguin::Ev namespace */
412 static void init_ev(VALUE mSleepyPenguin
)
415 * Document-module: SleepyPenguin::Ev
417 * Constants in the SleepyPenguin::Ev namespace are for the +flags+
418 * field in Kevent structs.
420 mEv
= rb_define_module_under(mSleepyPenguin
, "Ev");
422 /* See EV_ADD in the kevent(2) man page */
423 rb_define_const(mEv
, "ADD", UINT2NUM(EV_ADD
));
425 /* See EV_ENABLE in the kevent(2) man page */
426 rb_define_const(mEv
, "ENABLE", UINT2NUM(EV_ENABLE
));
428 /* See EV_DISABLE in the kevent(2) man page */
429 rb_define_const(mEv
, "DISABLE", UINT2NUM(EV_DISABLE
));
431 /* See EV_DISPATCH in the kevent(2) man page */
432 rb_define_const(mEv
, "DISPATCH", UINT2NUM(EV_DISPATCH
));
434 /* See EV_DELETE in the kevent(2) man page */
435 rb_define_const(mEv
, "DELETE", UINT2NUM(EV_DELETE
));
437 /* See EV_RECEIPT in the kevent(2) man page */
438 rb_define_const(mEv
, "RECEIPT", UINT2NUM(EV_RECEIPT
));
440 /* See EV_ONESHOT in the kevent(2) man page */
441 rb_define_const(mEv
, "ONESHOT", UINT2NUM(EV_ONESHOT
));
443 /* See EV_CLEAR in the kevent(2) man page */
444 rb_define_const(mEv
, "CLEAR", UINT2NUM(EV_CLEAR
));
446 /* See EV_EOF in the kevent(2) man page */
447 rb_define_const(mEv
, "EOF", UINT2NUM(EV_EOF
));
449 /* This is a return value in the proc passed to kevent */
450 rb_define_const(mEv
, "ERROR", UINT2NUM(EV_ERROR
));
453 /* initialize constants in the SleepyPenguin::EvFilt namespace */
454 static void init_evfilt(VALUE mSleepyPenguin
)
457 * Document-module: SleepyPenguin::EvFilt
459 * Pre-defined system filters for Kqueue events. Not all filters
460 * are supported on all platforms. Consult the kevent(2) man page
461 * and source code for your operating system for more information.
463 mEvFilt
= rb_define_module_under(mSleepyPenguin
, "EvFilt");
465 /* See EVFILT_READ in the kevent(2) man page */
466 rb_define_const(mEvFilt
, "READ", INT2NUM(EVFILT_READ
));
468 /* See EVFILT_WRITE in the kevent(2) man page */
469 rb_define_const(mEvFilt
, "WRITE", INT2NUM(EVFILT_WRITE
));
472 * See EVFILT_AIO in the kevent(2) man page, not supported by libkqueue
474 rb_define_const(mEvFilt
, "AIO", INT2NUM(EVFILT_AIO
));
476 /* See EVFILT_VNODE in the kevent(2) man page */
477 rb_define_const(mEvFilt
, "VNODE", INT2NUM(EVFILT_VNODE
));
480 /* Monitor process IDs, not supported by libkqueue */
481 rb_define_const(mEvFilt
, "PROC", INT2NUM(EVFILT_PROC
));
485 * Note: the use of EvFilt::SIGNAL is NOT supported in Ruby
486 * Ruby runtimes already manage all signal handling in the process,
487 * so attempting to manage them with a kqueue causes conflicts.
488 * We disable the Linux SignalFD interface for the same reason.
490 rb_define_const(mEvFilt
, "SIGNAL", INT2NUM(EVFILT_SIGNAL
));
492 /* See EVFILT_TIMER in the kevent(2) man page */
493 rb_define_const(mEvFilt
, "TIMER", INT2NUM(EVFILT_TIMER
));
496 /* network devices, no longer supported */
497 rb_define_const(mEvFilt
, "NETDEV", INT2NUM(EVFILT_NETDEV
));
502 * See EVFILT_FS in the kevent(2) man page,
503 * not supported by libkqueue
505 rb_define_const(mEvFilt
, "FS", INT2NUM(EVFILT_FS
));
509 /* attached to lio requests, not supported by libkqueue */
510 rb_define_const(mEvFilt
, "LIO", INT2NUM(EVFILT_LIO
));
513 /* see EVFILT_USER in the kevent(2) man page */
514 rb_define_const(mEvFilt
, "USER", INT2NUM(EVFILT_USER
));
517 /* initialize constants in the SleepyPenguin::Note namespace */
518 static void init_note(VALUE mSleepyPenguin
)
521 * Document-module: SleepyPenguin::Note
523 * Data/hint flags/masks for EVFILT_USER and friends in Kqueue
524 * On input, the top two bits of fflags specifies how the lower
525 * twenty four bits should be applied to the stored value of fflags.
527 * On output, the top two bits will always be set to Note::FFNOP
528 * and the remaining twenty four bits will contain the stored
531 mNote
= rb_define_module_under(mSleepyPenguin
, "Note");
533 /* ignore input fflags */
534 rb_define_const(mNote
, "FFNOP", UINT2NUM(NOTE_FFNOP
));
536 /* bitwise AND fflags */
537 rb_define_const(mNote
, "FFAND", UINT2NUM(NOTE_FFAND
));
539 /* bitwise OR fflags */
540 rb_define_const(mNote
, "FFOR", UINT2NUM(NOTE_FFOR
));
543 rb_define_const(mNote
, "FFCOPY", UINT2NUM(NOTE_FFCOPY
));
545 /* control mask for fflags */
546 rb_define_const(mNote
, "FFCTRLMASK", UINT2NUM(NOTE_FFCTRLMASK
));
548 /* user-defined flag mask for fflags */
549 rb_define_const(mNote
, "FFLAGSMASK", UINT2NUM(NOTE_FFLAGSMASK
));
551 /* Cause the event to be triggered for output */
552 rb_define_const(mNote
, "TRIGGER", UINT2NUM(NOTE_TRIGGER
));
556 * data/hint flags for EVFILT_{READ|WRITE}, shared with userspace
557 * Not supported by libkqueue in Linux
559 rb_define_const(mNote
, "LOWAT", UINT2NUM(NOTE_LOWAT
));
563 /* vnode was removed */
564 rb_define_const(mNote
, "DELETE", UINT2NUM(NOTE_DELETE
));
566 /* vnode data contents changed */
567 rb_define_const(mNote
, "WRITE", UINT2NUM(NOTE_WRITE
));
569 /* vnode size increased */
570 rb_define_const(mNote
, "EXTEND", UINT2NUM(NOTE_EXTEND
));
572 /* vnode attributes changes */
573 rb_define_const(mNote
, "ATTRIB", UINT2NUM(NOTE_ATTRIB
));
575 /* vnode link count changed */
576 rb_define_const(mNote
, "LINK", UINT2NUM(NOTE_LINK
));
578 /* vnode was renamed */
579 rb_define_const(mNote
, "RENAME", UINT2NUM(NOTE_RENAME
));
582 /* vnode access was revoked, not supported on Linux */
583 rb_define_const(mNote
, "REVOKE", UINT2NUM(NOTE_REVOKE
));
585 #endif /* EVFILT_VNODE */
589 rb_define_const(mNote
, "EXIT", UINT2NUM(NOTE_EXIT
));
592 rb_define_const(mNote
, "FORK", UINT2NUM(NOTE_FORK
));
595 rb_define_const(mNote
, "EXEC", UINT2NUM(NOTE_EXEC
));
597 /* mask for hint bits */
598 rb_define_const(mNote
, "PCTRLMASK", UINT2NUM(NOTE_PCTRLMASK
));
601 rb_define_const(mNote
, "PDATAMASK", UINT2NUM(NOTE_PDATAMASK
));
603 /* follow across forks */
604 rb_define_const(mNote
, "TRACK", UINT2NUM(NOTE_TRACK
));
606 /* could not track child */
607 rb_define_const(mNote
, "TRACKERR", UINT2NUM(NOTE_TRACKERR
));
609 /* am a child process */
610 rb_define_const(mNote
, "CHILD", UINT2NUM(NOTE_CHILD
));
611 #endif /* EVFILT_PROC */
615 rb_define_const(mNote
, "LINKUP", UINT2NUM(NOTE_LINKUP
));
618 rb_define_const(mNote
, "LINKDOWN", UINT2NUM(NOTE_LINKDOWN
));
620 /* link state is valid */
621 rb_define_const(mNote
, "LINKINV", UINT2NUM(NOTE_LINKINV
));
622 #endif /* EVFILT_NETDEV */
625 static void init_vq(VALUE mSleepyPenguin
)
629 * Document-module: SleepyPenguin::VQ
631 * Constants used by the EvFilt::FS filter in the Kqueue interfaces
633 mVQ
= rb_define_module_under(mSleepyPenguin
, "VQ");
636 rb_define_const(mVQ
, "NOTRESP", UINT2NUM(VQ_NOTRESP
));
638 /* server bad auth */
639 rb_define_const(mVQ
, "NEEDAUTH", UINT2NUM(VQ_NEEDAUTH
));
642 rb_define_const(mVQ
, "LOWDISK", UINT2NUM(VQ_LOWDISK
));
644 /* new filesystem mounted */
645 rb_define_const(mVQ
, "MOUNT", UINT2NUM(VQ_MOUNT
));
647 /* filesystem unmounted */
648 rb_define_const(mVQ
, "UNMOUNT", UINT2NUM(VQ_UNMOUNT
));
650 /* filesystem dead, needs force unmount */
651 rb_define_const(mVQ
, "DEAD", UINT2NUM(VQ_DEAD
));
653 /* filesystem needs assistance from external program */
654 rb_define_const(mVQ
, "ASSIST", UINT2NUM(VQ_ASSIST
));
656 /* server lockd down */
657 rb_define_const(mVQ
, "NOTRESPLOCK", UINT2NUM(VQ_NOTRESPLOCK
));
658 #endif /* VQ_NOTRESP */
661 void sleepy_penguin_init_kqueue(void)
663 VALUE mSleepyPenguin
, cKqueue
, cKqueue_IO
;
665 mSleepyPenguin
= rb_define_module("SleepyPenguin");
666 init_ev(mSleepyPenguin
);
667 init_evfilt(mSleepyPenguin
);
668 init_note(mSleepyPenguin
);
669 init_vq(mSleepyPenguin
);
671 cKqueue
= rb_define_class_under(mSleepyPenguin
, "Kqueue", rb_cObject
);
674 * Document-class: SleepyPenguin::Kqueue::IO
676 * Kqueue::IO is a low-level class. It does not provide fork nor
677 * GC-safety, so Ruby IO objects added via kevent must be retained
678 * by the application until IO#close is called.
680 * Warning: this class is easy to misuse, be careful as failure
681 * to preserve references objects passed as Kevent#udata may lead
682 * to crashes in Ruby. The high-level Kqueue class prevents these
683 * crashes (but may still return invalid objects).
685 cKqueue_IO
= rb_define_class_under(cKqueue
, "IO", rb_cIO
);
686 rb_define_singleton_method(cKqueue_IO
, "new", s_new
, 0);
688 rb_define_method(cKqueue_IO
, "kevent", sp_kevent
, -1);
690 id_for_fd
= rb_intern("for_fd");
693 * the high-level interface is implemented in Ruby
694 * see lib/sleepy_penguin/kevent.rb
697 #endif /* HAVE_SYS_EVENT_H */