/sample/signal-test
/sample/time-test
/sample/event-test
+/sample/watch-timing
/test-driver
/test/bench
include/event2/event.h
include/event2/event_compat.h
include/event2/event_struct.h
+ include/event2/watch.h
include/event2/http.h
include/event2/http_compat.h
include/event2/http_struct.h
evutil.c
evutil_rand.c
evutil_time.c
+ watch.c
listener.c
log.c
signal.c
hello-world
signal-test
http-connect
- time-test)
+ time-test
+ watch-timing)
foreach(SAMPLE ${SAMPLES})
add_sample_prog(OFF ${SAMPLE} sample/${SAMPLE}.c)
endforeach()
+ if (NOT WIN32)
+ target_link_libraries(watch-timing m)
+ endif()
if (NOT EVENT__DISABLE_OPENSSL)
add_sample_prog(ON https-client
test/regress_testutils.c
test/regress_testutils.h
test/regress_util.c
+ test/regress_watch.c
test/tinytest.c)
if (WIN32)
evutil.c \
evutil_rand.c \
evutil_time.c \
+ watch.c \
listener.c \
log.c \
$(SYS_SRC)
#endif
#include "event2/event-config.h"
+#include "event2/watch.h"
#include "evconfig-private.h"
#include <time.h>
void *arg;
};
+/** Contextual information passed from event_base_loop to the "prepare" watcher
+ * callbacks. We define this as a struct rather than individual parameters to
+ * the callback function for the sake of future extensibility. */
+struct evwatch_prepare_cb_info {
+ /** The timeout duration passed to the underlying implementation's `dispatch`.
+ * See evwatch_prepare_get_timeout. */
+ const struct timeval *timeout;
+};
+
+/** Contextual information passed from event_base_loop to the "check" watcher
+ * callbacks. We define this as a struct rather than individual parameters to
+ * the callback function for the sake of future extensibility. */
+struct evwatch_check_cb_info {
+ /** Placeholder, since empty struct is not allowed by some compilers. */
+ void *unused;
+};
+
+/** Watcher types (prepare and check, perhaps others in the future). */
+#define EVWATCH_PREPARE 0
+#define EVWATCH_CHECK 1
+#define EVWATCH_MAX 2
+
+/** Handle to a "prepare" or "check" callback, registered in event_base. */
+union evwatch_cb {
+ evwatch_prepare_cb prepare;
+ evwatch_check_cb check;
+};
+struct evwatch {
+ /** Tail queue pointers, called "next" by convention in libevent.
+ * See <sys/queue.h> */
+ TAILQ_ENTRY(evwatch) next;
+
+ /** Pointer to owning event loop */
+ struct event_base *base;
+
+ /** Watcher type (see above) */
+ unsigned type;
+
+ /** Callback function */
+ union evwatch_cb callback;
+
+ /** User-defined argument for callback function */
+ void *arg;
+};
+TAILQ_HEAD(evwatch_list, evwatch);
+
struct event_base {
/** Function pointers and other data to describe this event_base's
* backend. */
/** List of event_onces that have not yet fired. */
LIST_HEAD(once_event_list, event_once) once_events;
+ /** "Prepare" and "check" watchers. */
+ struct evwatch_list watchers[EVWATCH_MAX];
};
struct event_config_entry {
#include "event2/event.h"
#include "event2/event_struct.h"
#include "event2/event_compat.h"
+#include "event2/watch.h"
#include "event-internal.h"
#include "defer-internal.h"
#include "evthread-internal.h"
event_base_start_iocp_(base, cfg->n_cpus_hint);
#endif
+ /* initialize watcher lists */
+ for (i = 0; i < EVWATCH_MAX; ++i)
+ TAILQ_INIT(&base->watchers[i]);
+
return (base);
}
{
int i, n_deleted=0;
struct event *ev;
+ struct evwatch *watcher;
/* XXXX grab the lock? If there is contention when one thread frees
* the base, then the contending thread will be very sad soon. */
EVTHREAD_FREE_LOCK(base->th_base_lock, 0);
EVTHREAD_FREE_COND(base->current_event_cond);
+ /* Free all event watchers */
+ for (i = 0; i < EVWATCH_MAX; ++i) {
+ while (!TAILQ_EMPTY(&base->watchers[i])) {
+ watcher = TAILQ_FIRST(&base->watchers[i]);
+ TAILQ_REMOVE(&base->watchers[i], watcher, next);
+ mm_free(watcher);
+ }
+ }
+
/* If we're freeing current_base, there won't be a current_base. */
if (base == current_base)
current_base = NULL;
struct timeval tv;
struct timeval *tv_p;
int res, done, retval = 0;
+ struct evwatch_prepare_cb_info prepare_info;
+ struct evwatch_check_cb_info check_info;
+ struct evwatch *watcher;
/* Grab the lock. We will release it inside evsel.dispatch, and again
- * as we invoke user callbacks. */
+ * as we invoke watchers and user callbacks. */
EVBASE_ACQUIRE_LOCK(base, th_base_lock);
if (base->running_loop) {
event_queue_make_later_events_active(base);
+ /* Invoke prepare watchers before polling for events */
+ EVBASE_RELEASE_LOCK(base, th_base_lock);
+ prepare_info.timeout = tv_p;
+ TAILQ_FOREACH(watcher, &base->watchers[EVWATCH_PREPARE], next)
+ (*watcher->callback.prepare)(watcher, &prepare_info, watcher->arg);
+ EVBASE_ACQUIRE_LOCK(base, th_base_lock);
+
clear_time_cache(base);
res = evsel->dispatch(base, tv_p);
update_time_cache(base);
+ /* Invoke check watchers after polling for events, and before
+ * processing them */
+ EVBASE_RELEASE_LOCK(base, th_base_lock);
+ TAILQ_FOREACH(watcher, &base->watchers[EVWATCH_CHECK], next)
+ (*watcher->callback.check)(watcher, &check_info, watcher->arg);
+ EVBASE_ACQUIRE_LOCK(base, th_base_lock);
+
timeout_process(base);
if (N_ACTIVE_CALLBACKS(base)) {
--- /dev/null
+/*
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#ifndef EVENT2_WATCH_H_INCLUDED_
+#define EVENT2_WATCH_H_INCLUDED_
+
+/** @file event2/watch.h
+
+ "Prepare" and "check" watchers. A "prepare" watcher is a callback that fires
+ immediately before polling for I/O. A "check" watcher is a callback that
+ fires immediately after polling and before processing any active events. This
+ may be useful for embedding other libraries' event loops (e.g. UI toolkits)
+ into libevent's.
+
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <event2/visibility.h>
+
+struct event_base;
+struct evwatch;
+struct evwatch_prepare_cb_info;
+struct evwatch_check_cb_info;
+struct timeval;
+
+/**
+ Prepare callback, invoked by event_base_loop immediately before polling for
+ I/O.
+
+ @param watcher the prepare watcher that invoked this callback.
+ @param info contextual information passed from event_base_loop.
+ @param arg additional user-defined argument, set in `evwatch_prepare_new`.
+ */
+typedef void (*evwatch_prepare_cb)(struct evwatch *, const struct evwatch_prepare_cb_info *, void *);
+
+/**
+ Check callback, invoked by event_base_loop immediately after polling for I/O
+ and before processing any active events.
+
+ @param watcher the check watcher that invoked this callback.
+ @param info contextual information passed from event_base_loop.
+ @param arg additional user-defined argument, set in `evwatch_check_new`.
+ */
+typedef void (*evwatch_check_cb)(struct evwatch *, const struct evwatch_check_cb_info *, void *);
+
+/**
+ Register a new "prepare" watcher, to be called in the event loop prior to
+ polling for events. Watchers will be called in the order they were
+ registered.
+
+ @param base the event_base to operate on.
+ @param callback the callback function to invoke.
+ @param arg additional user-defined argument provided to the callback.
+ @return a pointer to the newly allocated event watcher.
+ */
+EVENT2_EXPORT_SYMBOL
+struct evwatch *evwatch_prepare_new(struct event_base *base, evwatch_prepare_cb callback, void *arg);
+
+/**
+ Register a new "check" watcher, to be called in the event loop after polling
+ for events and before handling them. Watchers will be called in the order
+ they were registered.
+
+ @param base the event_base to operate on.
+ @param callback the callback function to invoke.
+ @param arg additional user-defined argument provided to the callback.
+ @return a pointer to the newly allocated event watcher.
+ */
+EVENT2_EXPORT_SYMBOL
+struct evwatch *evwatch_check_new(struct event_base *base, evwatch_check_cb callback, void *arg);
+
+/**
+ Get the event_base that a given evwatch is registered with.
+
+ @param watcher the watcher to get the event_base for.
+ @return the event_base for the given watcher.
+ */
+EVENT2_EXPORT_SYMBOL
+struct event_base *evwatch_base(struct evwatch *watcher);
+
+/**
+ Deregister and deallocate a watcher. Any watchers not freed using
+ evwatch_free will eventually be deallocated in event_base_free
+ (calling evwatch_free on a watcher after event_base_free has been
+ called on its corresponding event_base is an error).
+
+ @param watcher the watcher to deregister and deallocate.
+ */
+EVENT2_EXPORT_SYMBOL
+void evwatch_free(struct evwatch *watcher);
+
+/**
+ Get the timeout (the expected polling duration) passed to the underlying
+ implementation's `dispatch`. This value will only be set if there are pending
+ EV_TIMEOUT events and if the event_base isn't in EVLOOP_NONBLOCK mode. It may
+ be a useful performance statistic to compare the expected polling duration
+ against the actual polling duration (that is, the time difference measured
+ between this prepare callback and the following check callback).
+
+ @param info the "prepare" callback info.
+ @param timeout address of a timeval to write the polling duration to.
+ @return 1 if a value was written to *timeout, or 0 if not.
+ */
+EVENT2_EXPORT_SYMBOL
+int evwatch_prepare_get_timeout(const struct evwatch_prepare_cb_info *info, struct timeval *timeout);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* EVENT2_WATCH_H_INCLUDED_ */
include/event2/event.h \
include/event2/event_compat.h \
include/event2/event_struct.h \
+ include/event2/watch.h \
include/event2/http.h \
include/event2/http_compat.h \
include/event2/http_struct.h \
sample/http-server \
sample/http-connect \
sample/signal-test \
- sample/time-test
+ sample/time-test \
+ sample/watch-timing
if OPENSSL
SAMPLES += sample/le-proxy
sample_http_server_LDADD = $(LIBEVENT_GC_SECTIONS) libevent.la
sample_http_connect_SOURCES = sample/http-connect.c
sample_http_connect_LDADD = $(LIBEVENT_GC_SECTIONS) libevent.la
+sample_watch_timing_SOURCES = sample/watch-timing.c
+sample_watch_timing_LDADD = $(LIBEVENT_GC_SECTIONS) libevent.la -lm
--- /dev/null
+#include <math.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#ifdef EVENT__HAVE_SYS_TIME_H
+#include <sys/time.h>
+#endif
+#include <time.h>
+
+#include <event2/event.h>
+#include <event2/util.h>
+#include <event2/watch.h>
+
+/**
+ An approximate histogram in constant space, based on Ben-Haim & Yom-Tov, "A
+ Streaming Parallel Decision Tree Algorithm" [1] and a previous implementation
+ in Java by Dan Rosen [2]. The histogram is represented as an array of
+ contiguous bins of non-uniform width. Each bin is centered on a certain point,
+ called its "centroid," and summarizes some "count" of observations. The bins
+ are ordered in the array by their centroids; an array is used rather than a
+ linked structure for CPU cache friendliness.
+
+ When the histogram is updated with a new observation, a new bin is created for
+ it, and then the pair of bins with the closest centroids are merged. Since
+ bins are stored in contiguous memory, this update process requires bins to be
+ shifted in worst-case linear time. The novel contribution of this
+ implementation is to maintain an insertion gap adjacent to the most recently
+ merged bin, such that for "well behaved" input (such as a normal
+ distribution), the number of shift operations required by an update should be
+ much less than the total number of bins on average.
+
+ This implementation is almost entirely untested. Don't trust it for
+ production code.
+
+ [1] http://www.jmlr.org/papers/volume11/ben-haim10a/ben-haim10a.pdf
+ [2] https://github.com/mergeconflict/histogram
+ */
+
+/** Compare two doubles for equality without the compiler warning. This is
+ * probably the wrong thing to do, but this is just sample code :) */
+static inline int
+eq(double a, double b)
+{
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wfloat-equal"
+ return a == b;
+#pragma GCC diagnostic pop
+}
+
+struct bin {
+ double centroid;
+ unsigned long count;
+};
+
+struct histogram {
+ struct bin *bins;
+ unsigned max_bins;
+ unsigned num_bins;
+ unsigned gap;
+ unsigned long count;
+ double min;
+ double max;
+};
+
+static struct histogram *
+histogram_new(unsigned max_bins)
+{
+ struct histogram *h = malloc(sizeof(struct histogram));
+ h->bins = calloc(max_bins + 1, sizeof(struct bin));
+ h->max_bins = max_bins;
+ h->num_bins = 0;
+ h->gap = 0;
+ h->count = 0;
+ h->min = INFINITY;
+ h->max = -INFINITY;
+ return h;
+}
+
+static void
+histogram_free(struct histogram *h)
+{
+ free(h->bins);
+ free(h);
+}
+
+static void
+histogram_update(struct histogram *h, double observation)
+{
+ unsigned bin;
+ double delta;
+ double min_delta = INFINITY;
+
+ /* Update population count, min and max */
+ ++(h->count);
+ if (observation < h->min)
+ h->min = observation;
+ if (observation > h->max)
+ h->max = observation;
+
+ /* Shift the insertion gap to the left or right so that the new bin
+ * containing the given observation as its centroid will be in the right
+ * order with respect to the other bins. */
+ while (1) {
+ /* Look at the bin to the left of the gap... */
+ if (h->gap != 0) {
+ /* If its centroid is greater than the observation, move
+ * the gap to the left and try again... */
+ if (h->bins[h->gap - 1].centroid > observation) {
+ h->bins[h->gap] = h->bins[h->gap - 1];
+ --(h->gap);
+ continue;
+ }
+ /* If its centroid is equal to the observation, just
+ * update its count in place. */
+ if (eq(h->bins[h->gap - 1].centroid, observation)) {
+ ++(h->bins[h->gap - 1].count);
+ return;
+ }
+ }
+
+ /* Look at the bin to the right of the gap... */
+ if (h->gap != h->num_bins) {
+ /* If its centroid is less than the observation, move
+ * the gap to the right and try again... */
+ if (h->bins[h->gap + 1].centroid < observation) {
+ h->bins[h->gap] = h->bins[h->gap + 1];
+ ++(h->gap);
+ continue;
+ }
+ /* If its centroid is equal to the observation, just
+ * update its count in place. */
+ if (eq(h->bins[h->gap + 1].centroid, observation)) {
+ ++(h->bins[h->gap + 1].count);
+ return;
+ }
+ }
+
+ /* If the gap is in the right place, we're ready to insert. */
+ break;
+ }
+
+ /* Insert the observation into a new bin at the gap. */
+ h->bins[h->gap].centroid = observation;
+ h->bins[h->gap].count = 1;
+
+ /* If the histogram isn't full yet, don't bother merging bins, just
+ * stick the gap back at the end. */
+ if (h->num_bins != h->max_bins) {
+ h->gap = ++(h->num_bins);
+ return;
+ }
+
+ /* Find the two adjacent bins with the closest centroids and merge them.
+ * The choice whether to leave the gap on the left or right is
+ * arbitrary (we choose the left). */
+ for (bin = 0; bin < h->num_bins; ++bin) {
+ delta = h->bins[bin + 1].centroid - h->bins[bin].centroid;
+ if (delta < min_delta) {
+ min_delta = delta;
+ h->gap = bin;
+ }
+ }
+ /* The merged centroid is the weighted average of the two, and the
+ * merged count is the sum of the two. */
+ h->bins[h->gap + 1].centroid =
+ (h->bins[h->gap].centroid * h->bins[h->gap].count +
+ h->bins[h->gap + 1].centroid * h->bins[h->gap + 1].count) /
+ (h->bins[h->gap].count + h->bins[h->gap + 1].count);
+ h->bins[h->gap + 1].count += h->bins[h->gap].count;
+}
+
+static double
+histogram_query(const struct histogram *h, double quantile)
+{
+ unsigned lhs = 0, rhs = 0;
+ struct bin lhs_bin = { 0, 0 }, rhs_bin = { 0, 0 };
+ double lhs_total = 0, rhs_total = 0;
+ double a = 0, b = 0, c = 0, z = 0;
+
+ /* The "needle" is the n'th value represented by the histogram. For
+ * example, if the histogram summarizes 100 entries and we're querying
+ * for the 50th percentile, the needle is 50. */
+ double needle = h->count * quantile;
+ if (quantile <= 0)
+ return h->min;
+ if (quantile >= 1)
+ return h->max;
+
+ /* Divide the histogram into slices: the first slice starts at h->min
+ * and ends at h->bins[0].centroid, the last slice starts at
+ * h->bins[h->num_bins].centroid and ends at h->max, and the slices
+ * in the middle are between adjacent centroids (minding the gap). The
+ * "count" in each slice is the average of the count in the two bins
+ * that define it. Find the slice containing the needle by keeping a
+ * running total of the slice counts. */
+ while (rhs_total < needle) {
+ /* Determine the left-hand side bin of the current slice. Note
+ * that the first slice has bin 0 on its right-hand side! */
+ if (rhs == 0) {
+ lhs_bin.centroid = h->min;
+ lhs_bin.count = 0;
+ } else {
+ lhs_bin = h->bins[lhs];
+ }
+
+ /* Determine the right-hand side bin of the current slice... */
+ if (rhs > h->num_bins) {
+ lhs_bin.centroid = h->max;
+ rhs_bin.count = 0;
+ } else {
+ rhs_bin = h->bins[rhs];
+ }
+
+ /* Update the running totals: the lhs total is whatever the rhs
+ * total was previously, and the new rhs total includes the
+ * count for this slice. */
+ lhs_total = rhs_total;
+ rhs_total += 0.5 * (lhs_bin.count + rhs_bin.count);
+
+ /* Next iteration's left-hand side is the current iteration's
+ * right-hand side, and next iteration's right-hand side is one
+ * bin further right (minding the gap). */
+ lhs = rhs++;
+ if (rhs == h->gap)
+ rhs++;
+ }
+
+ /* Approximate the value at the requested quantile... */
+ a = rhs_bin.count - lhs_bin.count;
+ if (eq(a, 0)) {
+ b = rhs_total - lhs_total;
+ z = eq(b, 0) ? 0 : (needle - lhs_total) / b;
+ } else {
+ b = 2 * lhs_bin.count;
+ c = 2 * (lhs_total - needle);
+ z = (-b + sqrt(b * b - 4 * a * c)) / (2 * a);
+ }
+ return lhs_bin.centroid + (rhs_bin.centroid - lhs_bin.centroid) * z;
+}
+
+/**
+ This is an example of one way in which "prepare" and "check" watchers can be
+ useful. We track histograms of two timing metrics:
+
+ The first is "duration," which is the amount of time between a "check" and the
+ next "prepare" (in the next iteration of the event loop). This corresponds
+ pretty closely to the amount of time spent in event handlers (such as the
+ `on_timeout` handler in this example). In a real-world server, this would
+ provide a way to monitor whether any of your handlers are blocking or
+ otherwise performing heavy computation.
+
+ The second is "delay," which is the difference between the actual and expected
+ polling duration. The actual polling duration is the amount of time between a
+ "prepare" and the next "check" (in the same iteration of the event loop), and
+ the expected duration is obtained from `evwatch_prepare_get_timeout`. In a
+ real-world server, this provides an indication of kernel scheduling delays.
+ For example, if your server is lightly loaded, this delay should usually be
+ close to your kernel's scheduling quantum (e.g. 1 millisecond).
+ */
+
+static struct event_base *base;
+static struct timeval
+ prepare_time = { 0, 0 },
+ check_time = { 0, 0 },
+ expected = { 0, 0 };
+static struct histogram *durations, *delays;
+
+static void on_prepare(struct evwatch *watcher, const struct evwatch_prepare_cb_info *info, void *arg)
+{
+ struct timeval duration;
+ evutil_gettimeofday(&prepare_time, NULL);
+ evwatch_prepare_get_timeout(info, &expected);
+ if (check_time.tv_sec != 0) {
+ evutil_timersub(&prepare_time, &check_time, &duration);
+ histogram_update(durations, duration.tv_sec + duration.tv_usec / 1000000.0l);
+ }
+}
+
+static void on_check(struct evwatch *watcher, const struct evwatch_check_cb_info *info, void *arg)
+{
+ struct timeval actual, delay;
+ evutil_gettimeofday(&check_time, NULL);
+ evutil_timersub(&check_time, &prepare_time, &actual);
+ evutil_timersub(&actual, &expected, &delay);
+ if (delay.tv_sec >= 0)
+ histogram_update(delays, delay.tv_sec + delay.tv_usec / 1000000.0l);
+}
+
+static void
+on_timeout(evutil_socket_t fd, short events, void *arg)
+{
+ printf("durations: [p50 = %fs, p95 = %fs], delays: [p50 = %fs, p95 = %fs]\n",
+ histogram_query(durations, 0.5),
+ histogram_query(durations, 0.95),
+ histogram_query(delays, 0.5),
+ histogram_query(delays, 0.95));
+}
+
+
+static void
+on_sigint(evutil_socket_t sig, short events, void *arg)
+{
+ event_base_loopbreak(base);
+}
+
+int
+main(int argc, char **argv)
+{
+ struct timeval one_second = { 1, 0 };
+ struct event *timeout_event, *sigint_event;
+
+ base = event_base_new();
+ durations = histogram_new(100);
+ delays = histogram_new(100);
+
+ /* add prepare and check watchers; no need to hang on to their pointers,
+ * since they will be freed for us in event_base_free. */
+ evwatch_prepare_new(base, &on_prepare, NULL);
+ evwatch_check_new(base, &on_check, NULL);
+
+ /* set a persistent one second timeout */
+ timeout_event = event_new(base, -1, EV_PERSIST, &on_timeout, NULL);
+ if (!timeout_event)
+ return EXIT_FAILURE;
+ event_add(timeout_event, &one_second);
+
+ /* set a handler for interrupt, so we can quit cleanly */
+ sigint_event = evsignal_new(base, SIGINT, &on_sigint, NULL);
+ if (!sigint_event)
+ return EXIT_FAILURE;
+ event_add(sigint_event, NULL);
+
+ /* run the event loop until interrupted */
+ event_base_dispatch(base);
+
+ /* clean up */
+ event_free(timeout_event);
+ event_free(sigint_event);
+ event_base_free(base);
+ histogram_free(durations);
+ histogram_free(delays);
+
+ return EXIT_SUCCESS;
+}
test/regress_testutils.c \
test/regress_testutils.h \
test/regress_util.c \
+ test/regress_watch.c \
test/tinytest.c \
$(regress_thread_SOURCES) \
$(regress_zlib_SOURCES)
extern struct testcase_t listener_testcases[];
extern struct testcase_t listener_iocp_testcases[];
extern struct testcase_t thread_testcases[];
+extern struct testcase_t watch_testcases[];
extern struct evutil_weakrand_state test_weakrand_state;
{ "rpc/", rpc_testcases },
{ "thread/", thread_testcases },
{ "listener/", listener_testcases },
+ { "watch/", watch_testcases },
#ifdef _WIN32
{ "iocp/", iocp_testcases },
{ "iocp/bufferevent/", bufferevent_iocp_testcases },
--- /dev/null
+/*
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdlib.h>
+#ifdef EVENT__HAVE_SYS_TIME_H
+#include <sys/time.h>
+#endif
+#include <time.h>
+
+#include "event2/event.h"
+#include "event2/watch.h"
+#include "regress.h"
+
+static int iteration = 0;
+static int prepare_callback_1_count = 0;
+static int prepare_callback_2_count = 0;
+static int check_callback_1_count = 0;
+static int check_callback_2_count = 0;
+static struct timeval start_time = { 0, 0 };
+static struct timeval end_time = { 0, 0 };
+static int user_arg = 8675309;
+
+static void
+prepare_callback_1(struct evwatch *watcher, const struct evwatch_prepare_cb_info *info, void *arg)
+{
+ struct timeval timeout;
+ int timeout_msec;
+
+ /* user argument should be passed properly */
+ tt_ptr_op(arg, ==, &user_arg);
+
+ ++prepare_callback_1_count;
+
+ /* prepare_callback_1 should always fire before prepare_callback_2, and
+ * before both check callbacks */
+ tt_int_op(prepare_callback_1_count, >, prepare_callback_2_count);
+ tt_int_op(prepare_callback_1_count, >, check_callback_1_count);
+ tt_int_op(prepare_callback_1_count, >, check_callback_2_count);
+
+ /* if we've just scheduled the timeout event at the beginning of the
+ * iteration, save the current time and assert that the timeout is
+ * roughly what we set (this won't be exact on some platforms) */
+ if (start_time.tv_sec == 0) {
+ event_base_gettimeofday_cached(evwatch_base(watcher), &start_time);
+ tt_int_op(evwatch_prepare_get_timeout(info, &timeout), ==, 1);
+
+ timeout_msec = (timeout.tv_sec * 1000) + (timeout.tv_usec / 1000);
+ tt_int_op(timeout_msec, >=, 995);
+ tt_int_op(timeout_msec, <=, 1005);
+ }
+end:
+ ;
+}
+
+static void
+prepare_callback_2(struct evwatch *watcher, const struct evwatch_prepare_cb_info *info, void *arg)
+{
+ /* user argument should be passed properly */
+ tt_ptr_op(arg, ==, &user_arg);
+
+ ++prepare_callback_2_count;
+
+ /* prepare_callback_2 should only fire on the first iteration, and
+ * should fire before both check callbacks */
+ tt_int_op(iteration, ==, 0);
+ tt_int_op(prepare_callback_2_count, >, check_callback_1_count);
+ tt_int_op(prepare_callback_2_count, >, check_callback_2_count);
+end:
+ ;
+}
+
+static void
+check_callback_1(struct evwatch *watcher, const struct evwatch_check_cb_info *info, void *arg)
+{
+ /* user argument should be passed properly */
+ tt_ptr_op(arg, ==, &user_arg);
+
+ ++check_callback_1_count;
+
+ /* check_callback_1 should always fire before check_callback_2 */
+ tt_int_op(check_callback_1_count, >, check_callback_2_count);
+
+ /* save the end time, in case the timeout fires this time through the
+ * event loop */
+ event_base_gettimeofday_cached(evwatch_base(watcher), &end_time);
+end:
+ ;
+}
+
+static void
+check_callback_2(struct evwatch *watcher, const struct evwatch_check_cb_info *info, void *arg)
+{
+ /* user argument should be passed properly */
+ tt_ptr_op(arg, ==, &user_arg);
+
+ ++check_callback_2_count;
+
+ /* check_callback_2 should only fire on the first iteration */
+ tt_int_op(iteration, ==, 0);
+end:
+ ;
+}
+
+static void
+timeout_callback(evutil_socket_t fd, short events, void *arg)
+{
+ /* the duration between the start and end times should be at least 1
+ * second */
+ tt_int_op(end_time.tv_sec, >=, start_time.tv_sec + 1);
+end:
+ ;
+}
+
+/**
+ This tests a few important properties of "prepare" and "check" watchers:
+ - Watchers should be called in the order they were registered.
+ - Prepare watchers should be called before check watchers.
+ - Freeing a watcher will stop callbacks to it, but not to other watchers.
+ - Reported durations should align with the registered timeouts.
+ - It should be possible to call back into libevent from a callback without a
+ recursive lock.
+ - If this test is compiled with ASAN or similar, this test also illustrates
+ that event_base_free will free any watchers not previously freed by
+ evwatch_free.
+ */
+static void
+test_callback_ordering(void *ptr)
+{
+ struct basic_test_data *data = ptr;
+ struct event_base *base = data->base;
+ struct evwatch *prepare_callback_2_watcher;
+ struct evwatch *check_callback_2_watcher;
+ struct timeval timeout;
+
+ /* install prepare and check watchers */
+ evwatch_prepare_new(base, &prepare_callback_1, &user_arg);
+ evwatch_check_new(base, &check_callback_1, &user_arg);
+ prepare_callback_2_watcher = evwatch_prepare_new(base, &prepare_callback_2, &user_arg);
+ check_callback_2_watcher = evwatch_check_new(base, &check_callback_2, &user_arg);
+
+ /* schedule an 1 second timeout event, and run the event loop until the
+ * timeout fires */
+ timeout.tv_sec = 1;
+ timeout.tv_usec = 0;
+ event_base_once(base, -1, EV_TIMEOUT, &timeout_callback, 0, &timeout);
+ event_base_dispatch(base);
+
+ /* second iteration: free two of the watchers, schedule a timeout and
+ * run the event loop again */
+ iteration = 1;
+ start_time.tv_sec = 0;
+ evwatch_free(prepare_callback_2_watcher);
+ evwatch_free(check_callback_2_watcher);
+ event_base_once(base, -1, EV_TIMEOUT, &timeout_callback, 0, &timeout);
+ event_base_dispatch(base);
+}
+
+static void
+prepare_callback_3(struct evwatch *watcher, const struct evwatch_prepare_cb_info *info, void *arg)
+{
+ /* timeout should not be written to */
+ struct timeval timeout = { 123, 456 };
+ tt_int_op(evwatch_prepare_get_timeout(info, &timeout), ==, 0);
+ tt_int_op(timeout.tv_sec, ==, 123);
+ tt_int_op(timeout.tv_usec, ==, 456);
+end:
+ ;
+}
+
+/**
+ Test that evwatch_prepare_get_timeout behaves correctly when there is no
+ timeout.
+ */
+static void
+test_timeout_unavailable(void *ptr)
+{
+ struct basic_test_data *data = ptr;
+ struct event_base *base = data->base;
+
+ evwatch_prepare_new(base, &prepare_callback_3, NULL);
+ event_base_dispatch(base);
+}
+
+#ifndef EVENT__DISABLE_MM_REPLACEMENT
+static void *
+bad_malloc(size_t sz)
+{
+ return NULL;
+}
+
+/**
+ Test that creating prepare and check watchers fails gracefully if we can't
+ allocate memory.
+ */
+static void
+test_malloc_failure(void *ptr)
+{
+ struct basic_test_data *data = ptr;
+ struct event_base *base = data->base;
+ struct evwatch *bad_prepare, *bad_check;
+
+ event_set_mem_functions(bad_malloc, realloc, free);
+ bad_prepare = evwatch_prepare_new(base, &prepare_callback_1, NULL);
+ tt_ptr_op(bad_prepare, ==, NULL);
+
+ bad_check = evwatch_check_new(base, &check_callback_1, NULL);
+ tt_ptr_op(bad_check, ==, NULL);
+
+ event_set_mem_functions(malloc, realloc, free);
+end:
+ ;
+}
+#endif
+
+struct testcase_t watch_testcases[] = {
+ BASIC(callback_ordering, TT_FORK|TT_NEED_BASE),
+ BASIC(timeout_unavailable, TT_FORK|TT_NEED_BASE),
+#ifndef EVENT__DISABLE_MM_REPLACEMENT
+ BASIC(malloc_failure, TT_FORK|TT_NEED_BASE),
+#endif
+ END_OF_TESTCASES
+};
--- /dev/null
+/*
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "event2/watch.h"
+#include "event-internal.h"
+#include "evthread-internal.h"
+
+static inline struct evwatch *
+evwatch_new(struct event_base *base, union evwatch_cb callback, void *arg, unsigned type)
+{
+ struct evwatch *watcher = mm_malloc(sizeof(struct evwatch));
+ if (!watcher)
+ return NULL;
+ watcher->base = base;
+ watcher->type = type;
+ watcher->callback = callback;
+ watcher->arg = arg;
+ EVBASE_ACQUIRE_LOCK(base, th_base_lock);
+ TAILQ_INSERT_TAIL(&base->watchers[type], watcher, next);
+ EVBASE_RELEASE_LOCK(base, th_base_lock);
+ return watcher;
+}
+
+struct evwatch *
+evwatch_prepare_new(struct event_base *base, evwatch_prepare_cb callback, void *arg)
+{
+ union evwatch_cb cb = { .prepare = callback };
+ return evwatch_new(base, cb, arg, EVWATCH_PREPARE);
+}
+
+struct evwatch *
+evwatch_check_new(struct event_base *base, evwatch_check_cb callback, void *arg)
+{
+ union evwatch_cb cb = { .check = callback };
+ return evwatch_new(base, cb, arg, EVWATCH_CHECK);
+}
+
+struct event_base *
+evwatch_base(struct evwatch *watcher)
+{
+ return watcher->base;
+}
+
+void
+evwatch_free(struct evwatch *watcher)
+{
+ EVBASE_ACQUIRE_LOCK(watcher->base, th_base_lock);
+ TAILQ_REMOVE(&watcher->base->watchers[watcher->type], watcher, next);
+ EVBASE_RELEASE_LOCK(watcher->base, th_base_lock);
+ mm_free(watcher);
+}
+
+int
+evwatch_prepare_get_timeout(const struct evwatch_prepare_cb_info *info, struct timeval *timeout)
+{
+ if (info->timeout) {
+ *timeout = *(info->timeout);
+ return 1;
+ }
+ return 0;
+}
AC_CONFIG_SUBDIRS([libevent])
...
-The space after the initial '"' is significant.
\ No newline at end of file
+The space after the initial '"' is significant.
+
+* "Prepare" and "check" watchers
+
+Libevent now has a new mechanism for hooking into the event loop: "prepare" and
+"check" watchers. A "prepare" watcher is a callback that fires immediately
+before polling for I/O. A "check" watcher is a callback that fires immediately
+after polling and before processing any active events. This may be useful for
+embedding other libraries' event loops (e.g. UI toolkits) into libevent's. It's
+also useful for monitoring server performance. For example, if you measure the
+time between "prepare" and "check," that is the polling duration; the difference
+between the expected and actual polling duration provides an indication of
+kernel scheduling delay. And if you measure the time between "check" and the
+next "prepare" (in the next iteration of the event loop), that is a good
+approximation of the amount of time handling events; this provides a convenient
+way to monitor whether any event handlers are blocking or otherwise performing
+heavy computation.
+
+The watcher API is defined in <event2/watch.h>. A concrete example of how
+watchers can help monitor server performance is available in
+"sample/watch-timing.c".