|
|
|
|
@@ -43,6 +43,9 @@
|
|
|
|
|
#include <sys/epoll.h>
|
|
|
|
|
#include <sys/timerfd.h>
|
|
|
|
|
#endif
|
|
|
|
|
#ifdef JANET_EV_KQUEUE
|
|
|
|
|
#include <sys/event.h>
|
|
|
|
|
#endif
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
typedef struct {
|
|
|
|
|
@@ -1567,6 +1570,208 @@ void janet_ev_deinit(void) {
|
|
|
|
|
* End epoll implementation
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#elif defined(JANET_EV_KQUEUE)
|
|
|
|
|
/* Definition from:
|
|
|
|
|
* https://github.com/wahern/cqueues/blob/master/src/lib/kpoll.c
|
|
|
|
|
* NetBSD uses intptr_t while others use void * for .udata */
|
|
|
|
|
#define EV_SETx(ev, a, b, c, d, e, f) EV_SET((ev), (a), (b), (c), (d), (e), ((__typeof__((ev)->udata))(f)))
|
|
|
|
|
#define JANET_KQUEUE_TF (EV_ADD | EV_ENABLE | EV_CLEAR | EV_ONESHOT)
|
|
|
|
|
|
|
|
|
|
/* NOTE:
|
|
|
|
|
* NetBSD doesn't like intervals less than 1 millisecond so lets make that the
|
|
|
|
|
* default anywhere JANET_KQUEUE_TS will be used. */
|
|
|
|
|
#ifdef __NetBSD__
|
|
|
|
|
#define JANET_KQUEUE_MIN_INTERVAL 1
|
|
|
|
|
#else
|
|
|
|
|
#define JANET_KQUEUE_MIN_INTERVAL 0
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#ifdef __FreeBSD__
|
|
|
|
|
#define JANET_KQUEUE_TS(timestamp) (timestamp)
|
|
|
|
|
#else
|
|
|
|
|
/* NOTE:
|
|
|
|
|
* NetBSD and OpenBSD expect things are always intervals, so fake that we have
|
|
|
|
|
* abstime capability by changing how a timestamp is used in all kqueue calls
|
|
|
|
|
* and defining absent macros. Additionally NetBSD expects intervals be
|
|
|
|
|
* greater than 1 millisecond, so correct all intervals to be at least 1
|
|
|
|
|
* millisecond under NetBSD. */
|
|
|
|
|
JanetTimestamp fix_interval(const JanetTimestamp ts) {
|
|
|
|
|
return ts >= JANET_KQUEUE_MIN_INTERVAL ? ts : JANET_KQUEUE_MIN_INTERVAL;
|
|
|
|
|
}
|
|
|
|
|
#define JANET_KQUEUE_TS(timestamp) (fix_interval((timestamp - ts_now())))
|
|
|
|
|
#define NOTE_MSECONDS 0
|
|
|
|
|
#define NOTE_ABSTIME 0
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* TODO: make this available be we using kqueue or epoll, instead of
|
|
|
|
|
* redefinining it for kqueue and epoll separately? */
|
|
|
|
|
static JanetTimestamp ts_now(void) {
|
|
|
|
|
struct timespec now;
|
|
|
|
|
janet_assert(-1 != clock_gettime(CLOCK_MONOTONIC, &now), "failed to get time");
|
|
|
|
|
uint64_t res = 1000 * now.tv_sec;
|
|
|
|
|
res += now.tv_nsec / 1000000;
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void add_kqueue_events(const struct kevent *events, int length) {
|
|
|
|
|
/* NOTE: Status should be equal to the amount of events added, which isn't
|
|
|
|
|
* always known since deletions or modifications occur. Can't use the
|
|
|
|
|
* eventlist argument for it to report to us what failed otherwise we may
|
|
|
|
|
* poll in events to handle! This code assumes atomicity, that kqueue can
|
|
|
|
|
* either succeed or fail, but never partially (which is seemingly how it
|
|
|
|
|
* works in practice). When encountering an "inbetween" state we currently
|
|
|
|
|
* just panic!
|
|
|
|
|
*
|
|
|
|
|
* The FreeBSD man page kqueue(2) shows a check through the change list to
|
|
|
|
|
* check if kqueue had an error with any of the events being pushed to
|
|
|
|
|
* change. Maybe we should do this, even tho the man page also doesn't
|
|
|
|
|
* note that kqueue actually does this. We do not do this at this time. */
|
|
|
|
|
int status;
|
|
|
|
|
status = kevent(janet_vm.kq, events, length, NULL, 0, NULL);
|
|
|
|
|
if (status == -1 && errno != EINTR)
|
|
|
|
|
janet_panicv(janet_ev_lasterr());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
JanetListenerState *janet_listen(JanetStream *stream, JanetListener behavior, int mask, size_t size, void *user) {
|
|
|
|
|
JanetListenerState *state = janet_listen_impl(stream, behavior, mask, size, user);
|
|
|
|
|
struct kevent kev[2];
|
|
|
|
|
|
|
|
|
|
int length = 0;
|
|
|
|
|
if (state->stream->_mask & JANET_ASYNC_LISTEN_READ) {
|
|
|
|
|
EV_SETx(&kev[length], stream->handle, EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, stream);
|
|
|
|
|
length++;
|
|
|
|
|
}
|
|
|
|
|
if (state->stream->_mask & JANET_ASYNC_LISTEN_WRITE) {
|
|
|
|
|
EV_SETx(&kev[length], stream->handle, EVFILT_WRITE, EV_ADD | EV_ENABLE, 0, 0, stream);
|
|
|
|
|
length++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (length > 0) {
|
|
|
|
|
add_kqueue_events(kev, length);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return state;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void janet_unlisten(JanetListenerState *state, int is_gc) {
|
|
|
|
|
JanetStream *stream = state->stream;
|
|
|
|
|
if (!(stream->flags & JANET_STREAM_CLOSED)) {
|
|
|
|
|
/* Use flag to indicate state is not registered in kqueue */
|
|
|
|
|
if (!(state->_mask & (1 << JANET_ASYNC_EVENT_COMPLETE))) {
|
|
|
|
|
int is_last = (state->_next == NULL && stream->state == state);
|
|
|
|
|
int op = is_last ? EV_DELETE : EV_DISABLE | EV_ADD;
|
|
|
|
|
struct kevent kev[2];
|
|
|
|
|
EV_SETx(&kev[1], stream->handle, EVFILT_WRITE, op, 0, 0, stream);
|
|
|
|
|
|
|
|
|
|
int length = 0;
|
|
|
|
|
if (stream->_mask & JANET_ASYNC_EVENT_WRITE) {
|
|
|
|
|
EV_SETx(&kev[length], stream->handle, EVFILT_WRITE, op, 0, 0, stream);
|
|
|
|
|
length++;
|
|
|
|
|
}
|
|
|
|
|
if (stream->_mask & JANET_ASYNC_EVENT_READ) {
|
|
|
|
|
EV_SETx(&kev[length], stream->handle, EVFILT_READ, op, 0, 0, stream);
|
|
|
|
|
length++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
add_kqueue_events(kev, length);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
janet_unlisten_impl(state, is_gc);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#define JANET_KQUEUE_TIMER_IDENT 1
|
|
|
|
|
#define JANET_KQUEUE_MAX_EVENTS 64
|
|
|
|
|
|
|
|
|
|
void janet_loop1_impl(int has_timeout, JanetTimestamp timeout) {
|
|
|
|
|
/* Construct our timer which is a definite time on the clock, not an
|
|
|
|
|
* interval, in milliseconds as that is `JanetTimestamp`'s precision. */
|
|
|
|
|
struct kevent timer;
|
|
|
|
|
if (janet_vm.timer_enabled || has_timeout) {
|
|
|
|
|
EV_SETx(&timer,
|
|
|
|
|
JANET_KQUEUE_TIMER_IDENT,
|
|
|
|
|
EVFILT_TIMER,
|
|
|
|
|
JANET_KQUEUE_TF,
|
|
|
|
|
NOTE_MSECONDS | NOTE_ABSTIME,
|
|
|
|
|
JANET_KQUEUE_TS(timeout), &janet_vm.timer);
|
|
|
|
|
add_kqueue_events(&timer, 1);
|
|
|
|
|
}
|
|
|
|
|
janet_vm.timer_enabled = has_timeout;
|
|
|
|
|
|
|
|
|
|
/* Poll for events */
|
|
|
|
|
int status;
|
|
|
|
|
struct kevent events[JANET_KQUEUE_MAX_EVENTS];
|
|
|
|
|
do {
|
|
|
|
|
status = kevent(janet_vm.kq, NULL, 0, events, JANET_KQUEUE_MAX_EVENTS, NULL);
|
|
|
|
|
} while (status == -1 && errno == EINTR);
|
|
|
|
|
if (status == -1)
|
|
|
|
|
JANET_EXIT("failed to poll events");
|
|
|
|
|
|
|
|
|
|
/* Step state machines */
|
|
|
|
|
for (int i = 0; i < status; i++) {
|
|
|
|
|
void *p = (void*) events[i].udata;
|
|
|
|
|
if (&janet_vm.timer == p) {
|
|
|
|
|
/* Timer expired, ignore */;
|
|
|
|
|
} else if (janet_vm.selfpipe == p) {
|
|
|
|
|
/* Self-pipe handling */
|
|
|
|
|
janet_ev_handle_selfpipe();
|
|
|
|
|
} else {
|
|
|
|
|
JanetStream *stream = p;
|
|
|
|
|
JanetListenerState *state = stream->state;
|
|
|
|
|
if (NULL != state) {
|
|
|
|
|
state->event = events + i;
|
|
|
|
|
JanetAsyncStatus statuses[4];
|
|
|
|
|
for (int i = 0; i < 4; i++)
|
|
|
|
|
statuses[i] = JANET_ASYNC_STATUS_NOT_DONE;
|
|
|
|
|
|
|
|
|
|
if (!(events[i].flags & EV_ERROR)) {
|
|
|
|
|
if (events[i].filter == EVFILT_WRITE)
|
|
|
|
|
statuses[0] = state->machine(state, JANET_ASYNC_EVENT_WRITE);
|
|
|
|
|
if (events[i].filter == EVFILT_READ)
|
|
|
|
|
statuses[1] = state->machine(state, JANET_ASYNC_EVENT_READ);
|
|
|
|
|
if ((events[i].flags & EV_EOF) && !(events[i].data > 0))
|
|
|
|
|
statuses[3] = state->machine(state, JANET_ASYNC_EVENT_HUP);
|
|
|
|
|
} else {
|
|
|
|
|
statuses[2] = state->machine(state, JANET_ASYNC_EVENT_ERR);
|
|
|
|
|
}
|
|
|
|
|
if (statuses[0] == JANET_ASYNC_STATUS_DONE ||
|
|
|
|
|
statuses[1] == JANET_ASYNC_STATUS_DONE ||
|
|
|
|
|
statuses[2] == JANET_ASYNC_STATUS_DONE ||
|
|
|
|
|
statuses[3] == JANET_ASYNC_STATUS_DONE)
|
|
|
|
|
janet_unlisten(state, 0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void janet_ev_init(void) {
|
|
|
|
|
janet_ev_init_common();
|
|
|
|
|
janet_ev_setup_selfpipe();
|
|
|
|
|
janet_vm.kq = kqueue();
|
|
|
|
|
janet_vm.timer_enabled = 0;
|
|
|
|
|
if (janet_vm.kq == -1) goto error;
|
|
|
|
|
struct kevent events[2];
|
|
|
|
|
/* Don't use JANET_KQUEUE_TS here, as even under FreeBSD we use intervals
|
|
|
|
|
* here. */
|
|
|
|
|
EV_SETx(&events[0],
|
|
|
|
|
JANET_KQUEUE_TIMER_IDENT,
|
|
|
|
|
EVFILT_TIMER,
|
|
|
|
|
JANET_KQUEUE_TF,
|
|
|
|
|
NOTE_MSECONDS, JANET_KQUEUE_MIN_INTERVAL, &janet_vm.timer);
|
|
|
|
|
EV_SETx(&events[1], janet_vm.selfpipe[0], EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, janet_vm.selfpipe);
|
|
|
|
|
add_kqueue_events(events, 2);
|
|
|
|
|
return;
|
|
|
|
|
error:
|
|
|
|
|
JANET_EXIT("failed to initialize event loop");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void janet_ev_deinit(void) {
|
|
|
|
|
janet_ev_deinit_common();
|
|
|
|
|
close(janet_vm.kq);
|
|
|
|
|
janet_ev_cleanup_selfpipe();
|
|
|
|
|
janet_vm.kq = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#else
|
|
|
|
|
|
|
|
|
|
#include <poll.h>
|
|
|
|
|
|