diff --git a/configure.in b/configure.in index 2669be12..7a93eb94 100644 --- a/configure.in +++ b/configure.in @@ -1213,6 +1213,36 @@ else fi fi +# ======== +# libevent +# ======== +AC_ARG_WITH(libevent, [ --with-libevent compile with libevent], + [if test "$withval" = yes; then enable_libevent=yes; else enable_libevent=no; fi]) + +cf_have_libevent=no +if test "$enable_libevent" = yes; then + AC_CHECK_HEADERS(event.h ev-event.h libev/event.h) + if test "$ac_cv_header_event_h" = yes; then + AC_CHECK_LIB(event, event_loop) + if test "$ac_cv_lib_event_event_loop" = yes; then + cf_have_libevent=libevent + fi + fi + if test "$cf_have_libevent" = no; then + if test "$ac_cv_header_event_h" = yes -o "$ac_cv_header_ev_event_h" = yes -o "$ac_cv_header_libev_event_h"; then + AC_CHECK_LIB(ev, event_loop) + if test "$ac_cv_lib_ev_event_loop" = yes; then + cf_have_libevent=libev + fi + fi + fi +fi +if test "$cf_have_libevent" != no; then + AC_HAVE_FUNCS(event_base_set event_get_version event_get_method event_base_free event_base_new event_reinit event_base_get_method event_config_set_flag event_get_struct_event_size) +fi +EL_LOG_CONFIG([CONFIG_LIBEVENT], [[libevent]], [[$cf_have_libevent]]) + + # Final SSL setup EL_CONFIG_DEPENDS(CONFIG_SSL, [CONFIG_OPENSSL CONFIG_GNUTLS CONFIG_NSS_COMPAT_OSSL], [SSL]) diff --git a/src/main/main.c b/src/main/main.c index d7eb9dea..cc0f4f13 100644 --- a/src/main/main.c +++ b/src/main/main.c @@ -302,6 +302,7 @@ terminate_all_subsystems(void) unregister_modules_options(main_modules); done_options(); done_event(); + terminate_select(); terminate_osdep(); #ifdef CONFIG_COMBINE free_combined(); diff --git a/src/main/select.c b/src/main/select.c index 3f15800d..6ef9b73f 100644 --- a/src/main/select.c +++ b/src/main/select.c @@ -31,6 +31,17 @@ #include #endif +#if (defined(HAVE_EVENT_H) || defined(HAVE_EV_EVENT_H) || defined(HAVE_LIBEV_EVENT_H)) && (defined(HAVE_LIBEVENT) || defined(HAVE_LIBEV)) && !defined(OPENVMS) && !defined(DOS) +#if defined(HAVE_EVENT_H) +#include +#elif defined(HAVE_EV_EVENT_H) +#include +#else +#include +#endif +#define USE_LIBEVENT +#endif + #ifdef HAVE_SYS_SELECT_H #include #endif @@ -59,11 +70,21 @@ do { \ #define FD_SETSIZE 1024 #endif +/* +#define DEBUG_CALLS +*/ + +static int n_threads = 0; + struct thread { select_handler_T read_func; select_handler_T write_func; select_handler_T error_func; void *data; +#ifdef USE_LIBEVENT + struct event *read_event; + struct event *write_event; +#endif }; #ifdef CONFIG_OS_WIN32 @@ -72,7 +93,7 @@ struct thread { #define FD_SETSIZE 4096 #endif -static struct thread threads[FD_SETSIZE]; +static struct thread *threads = NULL; static fd_set w_read; static fd_set w_write; @@ -138,6 +159,192 @@ check_bottom_halves(void) } } +static void +restrict_fds(void) +{ +#if defined(RLIMIT_OFILE) && !defined(RLIMIT_NOFILE) +#define RLIMIT_NOFILE RLIMIT_OFILE +#endif +#if defined(HAVE_GETRLIMIT) && defined(HAVE_SETRLIMIT) && defined(RLIMIT_NOFILE) + struct rlimit limit; + int rs; + EINTRLOOP(rs, getrlimit(RLIMIT_NOFILE, &limit)); + if (rs) + goto skip_limit; + if (limit.rlim_cur > FD_SETSIZE) { + limit.rlim_cur = FD_SETSIZE; + EINTRLOOP(rs, setrlimit(RLIMIT_NOFILE, &limit)); + } +skip_limit:; +#endif +} + +#ifdef USE_LIBEVENT + +int event_enabled = 0; + +#ifndef HAVE_EVENT_GET_STRUCT_EVENT_SIZE +#define sizeof_struct_event sizeof(struct event) +#else +#define sizeof_struct_event (event_get_struct_event_size()) +#endif + +static inline +struct event *timer_event(struct timer *tm) +{ + return (struct event *)((unsigned char *)tm - sizeof_struct_event); +} + +#ifdef HAVE_EVENT_BASE_SET +struct event_base *event_base; +#endif + +static void +event_callback(int h, short ev, void *data) +{ +#ifndef EV_PERSIST + if (event_add((struct event *)data, NULL) == -1) + elinks_internal("ERROR: event_add failed: %s", strerror(errno)); +#endif + if (!(ev & EV_READ) == !(ev & EV_WRITE)) + elinks_internal("event_callback: invalid flags %d on handle %d", (int)ev, h); + if (ev & EV_READ) { +#if defined(HAVE_LIBEV) + /* Old versions of libev badly interact with fork and fire + * events spuriously. */ + if (ev_version_major() < 4 && !can_read(h)) return; +#endif + threads[h].read_func(threads[h].data); + } else if (ev & EV_WRITE) { +#if defined(HAVE_LIBEV) + /* Old versions of libev badly interact with fork and fire + * events spuriously. */ + if (ev_version_major() < 4 && !can_write(h)) return; +#endif + threads[h].write_func(threads[h].data); + } + check_bottom_halves(); +} + +static void +set_event_for_action(int h, void (*func)(void *), struct event **evptr, short evtype) +{ + if (func) { + if (!*evptr) { +#ifdef EV_PERSIST + evtype |= EV_PERSIST; +#endif + *evptr = mem_alloc(sizeof_struct_event); + event_set(*evptr, h, evtype, event_callback, *evptr); +#ifdef HAVE_EVENT_BASE_SET + if (event_base_set(event_base, *evptr) == -1) + elinks_internal("ERROR: event_base_set failed: %s, handle %d", strerror(errno), h); +#endif + } + if (event_add(*evptr, NULL) == -1) + elinks_internal("ERROR: event_add failed: %s, handle %d", strerror(errno), h); + } else { + if (*evptr) { + if (event_del(*evptr) == -1) + elinks_internal("ERROR: event_del failed: %s, handle %d", strerror(errno), h); + } + } +} + +static void +set_events_for_handle(int h) +{ + set_event_for_action(h, threads[h].read_func, &threads[h].read_event, EV_READ); + set_event_for_action(h, threads[h].write_func, &threads[h].write_event, EV_WRITE); +} + +static void +enable_libevent(void) +{ + int i; + + if (0 /* disable_libevent */) + return; + +#if !defined(NO_FORK_ON_EXIT) && defined(HAVE_KQUEUE) && !defined(HAVE_EVENT_REINIT) + /* kqueue doesn't work after fork */ + if (!F) + return; +#endif + +#if defined(HAVE_EVENT_CONFIG_SET_FLAG) + { + struct event_config *cfg; + cfg = event_config_new(); + if (!cfg) + return; + if (event_config_set_flag(cfg, EVENT_BASE_FLAG_NOLOCK) == -1) { + event_config_free(cfg); + return; + } + event_base = event_base_new_with_config(cfg); + event_config_free(cfg); + if (!event_base) + return; + } +#elif defined(HAVE_EVENT_BASE_NEW) + event_base = event_base_new(); + if (!event_base) + return; +#elif defined(HAVE_EVENT_BASE_SET) + event_base = event_init(); + if (!event_base) + return; +#else + event_init(); +#endif + event_enabled = 1; + + for (i = 0; i < w_max; i++) + set_events_for_handle(i); + +/* + foreach(tm, timers) + set_event_for_timer(tm); +*/ + set_events_for_timer(); +} + +static void +terminate_libevent(void) +{ + int i; + if (event_enabled) { + for (i = 0; i < n_threads; i++) { + set_event_for_action(i, NULL, &threads[i].read_event, EV_READ); + if (threads[i].read_event) + mem_free(threads[i].read_event); + set_event_for_action(i, NULL, &threads[i].write_event, EV_WRITE); + if (threads[i].write_event) + mem_free(threads[i].write_event); + } +#ifdef HAVE_EVENT_BASE_FREE + event_base_free(event_base); +#endif + event_enabled = 0; + } +} + +static void +do_event_loop(int flags) +{ + int e; +#ifdef HAVE_EVENT_BASE_SET + e = event_base_loop(event_base, flags); +#else + e = event_loop(flags); +#endif + if (e == -1) + elinks_internal("ERROR: event_base_loop failed: %s", strerror(errno)); +} +#endif + + select_handler_T get_handler(int fd, enum select_handler_type tp) { @@ -183,11 +390,44 @@ set_handlers(int fd, select_handler_T read_func, select_handler_T write_func, error_func = NULL; } #endif /* __GNU__ */ + +#if defined(USE_POLL) && defined(USE_LIBEVENT) + if (!event_enabled) +#endif + if (fd >= (int)FD_SETSIZE) { + elinks_internal("too big handle %d", fd); + return; + } + if (fd >= n_threads) { + threads = mem_realloc(threads, (fd + 1) * sizeof(struct thread)); + memset(threads + n_threads, 0, (fd + 1 - n_threads) * sizeof(struct thread)); + n_threads = fd + 1; + } + threads[fd].read_func = read_func; threads[fd].write_func = write_func; threads[fd].error_func = error_func; threads[fd].data = data; + if (read_func || write_func || error_func) { + if (fd >= w_max) w_max = fd + 1; + } else if (fd == w_max - 1) { + int i; + + for (i = fd - 1; i >= 0; i--) + if (FD_ISSET(i, &w_read) + || FD_ISSET(i, &w_write) + || FD_ISSET(i, &w_error)) + break; + w_max = i + 1; + } + +#ifdef USE_LIBEVENT + if (event_enabled) { + set_events_for_handle(fd); + return; + } +#endif if (read_func) { FD_SET(fd, &w_read); } else { @@ -208,19 +448,6 @@ set_handlers(int fd, select_handler_T read_func, select_handler_T write_func, FD_CLR(fd, &w_error); FD_CLR(fd, &x_error); } - - if (read_func || write_func || error_func) { - if (fd >= w_max) w_max = fd + 1; - } else if (fd == w_max - 1) { - int i; - - for (i = fd - 1; i >= 0; i--) - if (FD_ISSET(i, &w_read) - || FD_ISSET(i, &w_write) - || FD_ISSET(i, &w_error)) - break; - w_max = i + 1; - } } void @@ -241,6 +468,27 @@ select_loop(void (*init)(void)) init(); check_bottom_halves(); +#ifdef USE_LIBEVENT + enable_libevent(); +#if defined(USE_POLL) + if (!event_enabled) { + restrict_fds(); + } +#endif + if (event_enabled) { + while (!program.terminate) { + check_signals(); + if (1 /*(!F)*/) { + do_event_loop(EVLOOP_NONBLOCK); + check_signals(); + redraw_all_terminals(); + } + if (program.terminate) break; + do_event_loop(EVLOOP_ONCE); + } + } else +#endif + while (!program.terminate) { struct timeval *timeout = NULL; int n, i, has_timer; @@ -392,3 +640,12 @@ can_write(int fd) { return can_read_or_write(fd, 1); } + +void +terminate_select(void) +{ +#ifdef USE_LIBEVENT + terminate_libevent(); +#endif + mem_free(threads); +} diff --git a/src/main/select.h b/src/main/select.h index c6bd367c..a40b4861 100644 --- a/src/main/select.h +++ b/src/main/select.h @@ -46,4 +46,5 @@ void set_handlers(int fd, int can_read(int fd); int can_write(int fd); +void terminate_select(void); #endif diff --git a/src/main/timer.c b/src/main/timer.c index 33b00737..2c348b38 100644 --- a/src/main/timer.c +++ b/src/main/timer.c @@ -4,6 +4,19 @@ #include "config.h" #endif +#include + +#if (defined(HAVE_EVENT_H) || defined(HAVE_EV_EVENT_H) || defined(HAVE_LIBEV_EVENT_H)) && (defined(HAVE_LIBEVENT) || defined(HAVE_LIBEV)) && !defined(OPENVMS) && !defined(DOS) +#if defined(HAVE_EVENT_H) +#include +#elif defined(HAVE_EV_EVENT_H) +#include +#else +#include +#endif +#define USE_LIBEVENT +#endif + #include "elinks.h" #include "main/select.h" @@ -68,6 +81,59 @@ check_timers(timeval_T *last_time) timeval_copy(last_time, &now); } +#ifdef HAVE_EVENT_BASE_SET +extern struct event_base *event_base; +#endif + +#ifdef USE_LIBEVENT +extern int event_enabled; +#ifndef HAVE_EVENT_GET_STRUCT_EVENT_SIZE +#define sizeof_struct_event sizeof(struct event) +#else +#define sizeof_struct_event (event_get_struct_event_size()) +#endif + +static void +timer_callback(int h, short ev, void *data) +{ + struct timer *tm = data; + tm->func(tm->data); + kill_timer(&tm); + check_bottom_halves(); +} + + +static inline +struct event *timer_event(struct timer *tm) +{ + return (struct event *)((unsigned char *)tm - sizeof_struct_event); +} +#endif + +static void +set_event_for_timer(timer_id_T tm) +{ +#ifdef USE_LIBEVENT + struct timeval tv; + struct event *ev = timer_event(tm); + timeout_set(ev, timer_callback, tm); +#ifdef HAVE_EVENT_BASE_SET + if (event_base_set(event_base, ev) == -1) + elinks_internal("ERROR: event_base_set failed: %s", strerror(errno)); +#endif + tv.tv_sec = tm->interval.sec; + tv.tv_usec = tm->interval.usec; +#if defined(HAVE_LIBEV) + if (!tm->interval.usec && ev_version_major() < 4) { + /* libev bug */ + tv.tv_usec = 1; + } +#endif + if (timeout_add(ev, &tv) == -1) + elinks_internal("ERROR: timeout_add failed: %s", strerror(errno)); +#endif +} + /* Install a timer that calls @func(@data) after @delay milliseconds. * Store to *@id either the ID of the new timer, or TIMER_ID_UNDEF if * the timer cannot be installed. (This function ignores the previous @@ -83,20 +149,34 @@ install_timer(timer_id_T *id, milliseconds_T delay, void (*func)(void *), void * assert(id && delay > 0); +#ifdef USE_LIBEVENT + { + unsigned char *q = mem_alloc(sizeof_struct_event + sizeof(struct timer)); + new_timer = (struct timer *)(q + sizeof_struct_event); + } +#else new_timer = mem_alloc(sizeof(*new_timer)); +#endif *id = (timer_id_T) new_timer; /* TIMER_ID_UNDEF is NULL */ if (!new_timer) return; timeval_from_milliseconds(&new_timer->interval, delay); new_timer->func = func; new_timer->data = data; +#ifdef USE_LIBEVENT + if (event_enabled) { + set_event_for_timer(new_timer); + add_to_list(timers, new_timer); + } else +#endif + { + foreach (timer, timers) { + if (timeval_cmp(&timer->interval, &new_timer->interval) >= 0) + break; + } - foreach (timer, timers) { - if (timeval_cmp(&timer->interval, &new_timer->interval) >= 0) - break; + add_at_pos(timer->prev, new_timer); } - - add_at_pos(timer->prev, new_timer); } void @@ -106,11 +186,16 @@ kill_timer(timer_id_T *id) assert(id != NULL); if (*id == TIMER_ID_UNDEF) return; - timer = *id; del_from_list(timer); - mem_free(timer); +#ifdef USE_LIBEVENT + if (event_enabled) + timeout_del(timer_event(timer)); + mem_free(timer_event(timer)); +#else + mem_free(timer); +#endif *id = TIMER_ID_UNDEF; } @@ -124,3 +209,15 @@ get_next_timer_time(timeval_T *t) return 0; } + + +void +set_events_for_timer(void) +{ +#ifdef USE_LIBEVENT + timer_id_T tm; + + foreach(tm, timers) + set_event_for_timer(tm); +#endif +} diff --git a/src/main/timer.h b/src/main/timer.h index 9eeb1145..e5523afe 100644 --- a/src/main/timer.h +++ b/src/main/timer.h @@ -22,5 +22,6 @@ void check_timers(timeval_T *last_time); void install_timer(timer_id_T *id, milliseconds_T delay, void (*)(void *), void *); void kill_timer(timer_id_T *id); int get_next_timer_time(timeval_T *t); +void set_events_for_timer(void); #endif