Import timeouts.c directly from William Ahern's git.

Imported from here: https://github.com/wahern/timeout

Imported as of upstream e5a9e8bfaa9c631bdc54002181795931b65bdc1a.

All sources unmodified.
This commit is contained in:
Nick Mathewson 2016-04-13 08:58:43 -04:00
parent 0e354ad459
commit 32e80ea3d3
22 changed files with 3634 additions and 0 deletions

View File

@ -73,3 +73,7 @@ readpassphrase.[ch]
Portable readpassphrase implementation from OpenSSH portable, version Portable readpassphrase implementation from OpenSSH portable, version
6.8p1. 6.8p1.
timeouts/
William Ahern's hierarchical timer-wheel implementation. MIT license.

View File

@ -10,6 +10,8 @@ EXTHEADERS = \
src/ext/tor_readpassphrase.h \ src/ext/tor_readpassphrase.h \
src/ext/strlcat.c \ src/ext/strlcat.c \
src/ext/strlcpy.c \ src/ext/strlcpy.c \
src/ext/timeouts/timeout.h \
src/ext/timeouts/timeout-debug.h \
src/ext/tinytest_macros.h \ src/ext/tinytest_macros.h \
src/ext/tor_queue.h \ src/ext/tor_queue.h \
src/ext/siphash.h src/ext/siphash.h
@ -148,3 +150,26 @@ noinst_HEADERS += $(LIBKECCAK_TINY_HDRS)
LIBKECCAK_TINY=src/ext/keccak-tiny/libkeccak-tiny.a LIBKECCAK_TINY=src/ext/keccak-tiny/libkeccak-tiny.a
noinst_LIBRARIES += $(LIBKECCAK_TINY) noinst_LIBRARIES += $(LIBKECCAK_TINY)
EXTRA_DIST += \
timeouts/bench/bench-add.lua \
timeouts/bench/bench-aux.lua \
timeouts/bench/bench.c \
timeouts/bench/bench-del.lua \
timeouts/bench/bench-expire.lua \
timeouts/bench/bench.h \
timeouts/bench/bench-heap.c \
timeouts/bench/bench-llrb.c \
timeouts/bench/bench.plt \
timeouts/bench/bench-wheel.c \
timeouts/bench/Rules.mk \
timeouts/lua/Rules.mk \
timeouts/lua/timeout-lua.c \
timeouts/Makefile \
timeouts/Rules.shrc \
timeouts/test-timeout.c
# XXXX Once we use timeouts, include this in an actual library.
EXTRA_DIST += \
timeouts/timeout-bitops.c \
timeout.c

68
src/ext/timeouts/Makefile Normal file
View File

@ -0,0 +1,68 @@
# NOTE: GNU Make 3.81 won't export MAKEFLAGS if .POSIX is specified, but
# Solaris make won't export MAKEFLAGS unless .POSIX is specified.
$(firstword ignore).POSIX:
.DEFAULT_GOAL = all
.SUFFIXES:
all:
#
# USER-MODIFIABLE MACROS
#
top_srcdir = .
top_builddir = .
CFLAGS = -O2 -march=native -g -Wall -Wextra -Wno-unused-parameter -Wno-unused-function
SOFLAGS = $$(auto_soflags)
LIBS = $$(auto_libs)
ALL_CPPFLAGS = -I$(top_srcdir) -DWHEEL_BIT=$(WHEEL_BIT) -DWHEEL_NUM=$(WHEEL_NUM) $(CPPFLAGS)
ALL_CFLAGS = $(CFLAGS)
ALL_SOFLAGS = $(SOFLAGS)
ALL_LDFLAGS = $(LDFLAGS)
ALL_LIBS = $(LIBS)
LUA_API = 5.3
LUA = lua
LUA51_CPPFLAGS = $(LUA_CPPFLAGS)
LUA52_CPPFLAGS = $(LUA_CPPFLAGS)
LUA53_CPPFLAGS = $(LUA_CPPFLAGS)
WHEEL_BIT = 6
WHEEL_NUM = 4
RM = rm -f
# END MACROS
SHRC = \
top_srcdir="$(top_srcdir)"; \
top_builddir="$(top_builddir)"; \
. "$${top_srcdir}/Rules.shrc"
LUA_APIS = 5.1 5.2 5.3
include $(top_srcdir)/lua/Rules.mk
include $(top_srcdir)/bench/Rules.mk
all: test-timeout
timeout.o: $(top_srcdir)/timeout.c
test-timeout.o: $(top_srcdir)/test-timeout.c
timeout.o test-timeout.o:
@$(SHRC); echo_cmd $(CC) $(ALL_CFLAGS) -c -o $@ $${top_srcdir}/$(@F:%.o=%.c) $(ALL_CPPFLAGS)
test-timeout: timeout.o test-timeout.o
@$(SHRC); echo_cmd $(CC) $(ALL_CPPFLAGS) $(ALL_CFLAGS) -o $@ timeout.o test-timeout.o
.PHONY: clean clean~
clean:
$(RM) $(top_builddir)/test-timeout $(top_builddir)/*.o
$(RM) -r $(top_builddir)/*.dSYM
clean~:
find $(top_builddir) $(top_srcdir) -name "*~" -exec $(RM) -- {} "+"

View File

@ -0,0 +1,40 @@
# convert to absolute paths
top_srcdir="$(cd "${top_srcdir}" && pwd -L)"
top_builddir="$(cd "${top_builddir}" && pwd -L)"
# Paths for Lua modules (benchmarks and installed modules)
export LUA_CPATH="${top_builddir}/lua/5.1/?.so;${top_builddir}/bench/?.so;;"
export LUA_PATH="${top_srcdir}/lua/?.lua;${top_srcdir}/bench/?.lua;;"
export LUA_CPATH_5_2="${top_builddir}/lua/5.2/?.so;${top_builddir}/bench/?.so;;"
export LUA_PATH_5_2="${top_srcdir}/lua/?.lua;${top_srcdir}/bench/?.lua;;"
export LUA_CPATH_5_3="${top_builddir}/lua/5.3/?.so;${top_builddir}/bench/?.so;;"
export LUA_PATH_5_3="${top_srcdir}/lua/?.lua;${top_srcdir}/bench/?.lua;;"
# preserve stdout so we can print commands to terminal
exec 9>&1;
echo_cmd() {
printf "%s\n" "$*" >&9;
"$@";
}
auto_soflags() {
case "$(uname -s)" in
Darwin)
printf -- "-bundle -undefined dynamic_lookup"
;;
*)
printf -- "-fPIC -shared"
;;
esac
}
auto_libs() {
case "$(uname -s)" in
Linux)
printf -- "-lrt"
;;
*)
;;
esac
}

View File

@ -0,0 +1,49 @@
BENCH_MODS = bench.so $(BENCH_ALGOS:%=bench-%.so)
BENCH_ALGOS = wheel heap llrb
BENCH_OPS = add del expire
$(top_builddir)/bench/bench.so: $(top_srcdir)/bench/bench.c
$(top_builddir)/bench/bench-wheel.so: $(top_srcdir)/bench/bench-wheel.c
$(top_builddir)/bench/bench-heap.so: $(top_srcdir)/bench/bench-heap.c
$(top_builddir)/bench/bench-llrb.so: $(top_srcdir)/bench/bench-llrb.c
$(BENCH_MODS:%=$(top_builddir)/bench/%): $(top_srcdir)/timeout.h $(top_srcdir)/timeout.c $(top_srcdir)/bench/bench.h
mkdir -p $(@D)
@$(SHRC); echo_cmd $(CC) -o $@ $(top_srcdir)/bench/$(@F:%.so=%.c) $(ALL_CPPFLAGS) $(ALL_CFLAGS) $(ALL_SOFLAGS) $(ALL_LDFLAGS) $(ALL_LIBS)
$(BENCH_OPS:%=$(top_builddir)/bench/wheel-%.dat): $(top_builddir)/bench/bench-wheel.so $(top_builddir)/bench/bench.so $(top_srcdir)/bench/bench-aux.lua
$(BENCH_OPS:%=$(top_builddir)/bench/heap-%.dat): $(top_builddir)/bench/bench-heap.so $(top_builddir)/bench/bench.so $(top_srcdir)/bench/bench-aux.lua
$(BENCH_OPS:%=$(top_builddir)/bench/llrb-%.dat): $(top_builddir)/bench/bench-llrb.so $(top_builddir)/bench/bench.so $(top_srcdir)/bench/bench-aux.lua
$(BENCH_ALGOS:%=$(top_builddir)/bench/%-add.dat): $(top_srcdir)/bench/bench-add.lua
@$(SHRC); echo_cmd cd $(@D) && echo_cmd $(LUA) $${top_srcdir}/bench/bench-add.lua $${top_builddir}/bench/bench-$(@F:%-add.dat=%).so > $(@F).tmp
mv $@.tmp $@
$(BENCH_ALGOS:%=$(top_builddir)/bench/%-del.dat): $(top_srcdir)/bench/bench-del.lua
@$(SHRC); echo_cmd cd $(@D) && echo_cmd $(LUA) $${top_srcdir}/bench/bench-del.lua $${top_builddir}/bench/bench-$(@F:%-del.dat=%).so > $(@F).tmp
mv $@.tmp $@
$(BENCH_ALGOS:%=$(top_builddir)/bench/%-expire.dat): $(top_srcdir)/bench/bench-expire.lua
@$(SHRC); echo_cmd cd $(@D) && echo_cmd $(LUA) $${top_srcdir}/bench/bench-expire.lua $${top_builddir}/bench/bench-$(@F:%-expire.dat=%).so > $(@F).tmp
mv $@.tmp $@
$(top_builddir)/bench/bench.eps: \
$(BENCH_OPS:%=$(top_builddir)/bench/wheel-%.dat) \
$(BENCH_OPS:%=$(top_builddir)/bench/heap-%.dat)
# $(BENCH_OPS:%=$(top_builddir)/bench/llrb-%.dat)
$(top_builddir)/bench/bench.eps: $(top_srcdir)/bench/bench.plt
@$(SHRC); echo_cmd cd $(@D) && echo_cmd gnuplot $${top_srcdir}/bench/bench.plt > $(@F).tmp
mv $@.tmp $@
$(top_builddir)/bench/bench.pdf: $(top_builddir)/bench/bench.eps
@$(SHRC); echo_cmd ps2pdf $${top_builddir}/bench/bench.eps $@
bench-mods: $(BENCH_MODS:%=$(top_builddir)/bench/%)
bench-all: $(top_builddir)/bench/bench.pdf
bench-clean:
$(RM) -r $(top_builddir)/bench/*.so $(top_builddir)/bench/*.dSYM
$(RM) $(top_builddir)/bench/*.dat $(top_builddir)/bench/*.tmp
$(RM) $(top_builddir)/bench/bench.{eps,pdf}

View File

@ -0,0 +1,30 @@
#!/usr/bin/env lua
local bench = require"bench"
local aux = require"bench-aux"
local lib = ... or aux.optenv("BENCH_L", "bench-wheel.so")
local limit = tonumber(aux.optenv("BENCH_N", 1000000))
local step = tonumber(aux.optenv("BENCH_S", limit / 100))
local exp_step = tonumber(aux.optenv("BENCH_E", 1.0))
local verbose = aux.toboolean(os.getenv("BENCH_V", false))
local B = bench.new(lib, count, nil, verbose)
local fill_count, fill_last = B:fill(limit)
for i=0,limit,step do
local exp_elapsed, fill_elapsed, fill_rate
-- expire all timeouts
--exp_elapsed = aux.time(B.expire, B, fill_count, fill_last * exp_step)
exp_elapsed = aux.time(B.del, B, 0, fill_count)
assert(B:empty())
-- add i timeouts
fill_elapsed, fill_count, fill_last = aux.time(B.fill, B, i)
assert(fill_count == i)
fill_rate = fill_elapsed > 0 and (fill_count / fill_elapsed) or 0
local fmt = verbose and "%d\t%f\t(%d/s)\t(exp:%f)" or "%d\t%f"
aux.say(fmt, i, fill_elapsed, fill_rate, exp_elapsed)
end

View File

@ -0,0 +1,30 @@
local bench = require"bench"
local clock = bench.clock
local aux = {}
local function time_return(begun, ...)
local duration = clock() - begun
return duration, ...
end
function aux.time(f, ...)
local begun = clock()
return time_return(begun, f(...))
end
function aux.say(...)
print(string.format(...))
end
function aux.toboolean(s)
return tostring(s):match("^[1TtYy]") and true or false
end
function aux.optenv(k, def)
local s = os.getenv(k)
return (s and #s > 0 and s) or def
end
return aux

View File

@ -0,0 +1,25 @@
#!/usr/bin/env lua
local bench = require"bench"
local aux = require"bench-aux"
local lib = ... or aux.optenv("BENCH_L", "bench-wheel.so")
local limit = tonumber(aux.optenv("BENCH_N", 1000000))
local step = tonumber(aux.optenv("BENCH_S", limit / 100))
local verbose = aux.toboolean(os.getenv("BENCH_V", false))
local B = bench.new(lib, count)
for i=0,limit,step do
-- add i timeouts
local fill_elapsed, fill_count = aux.time(B.fill, B, i, 60 * 1000000)
assert(i == fill_count)
--- delete i timeouts
local del_elapsed = aux.time(B.del, B, 0, fill_count)
assert(B:empty())
local del_rate = i > 0 and i / del_elapsed or 0
local fmt = verbose and "%d\t%f\t(%d/s)\t(fill:%f)" or "%d\t%f"
aux.say(fmt, i, del_elapsed, del_rate, fill_elapsed)
end

View File

@ -0,0 +1,29 @@
#!/usr/bin/env lua
local bench = require"bench"
local aux = require"bench-aux"
local lib = ... or aux.optenv("BENCH_L", "bench-wheel.so")
local limit = tonumber(aux.optenv("BENCH_N", 1000000))
local step = tonumber(aux.optenv("BENCH_S", limit / 100))
-- expire 1/1000 * #timeouts per clock update
local exp_step = tonumber(aux.optenv("BENCH_E", 0.0001))
local verbose = aux.toboolean(os.getenv("BENCH_V", false))
local B = require"bench".new(lib, count)
for i=0,limit,step do
-- add i timeouts
local fill_elapsed, fill_count, fill_last = aux.time(B.fill, B, i)
-- expire timeouts by iteratively updating clock. exp_step is the
-- approximate number of timeouts (as a fraction of the total number
-- of timeouts) that will expire per update.
local exp_elapsed, exp_count = aux.time(B.expire, B, fill_count, math.floor(fill_last * exp_step))
assert(exp_count == i)
assert(B:empty())
local exp_rate = i > 0 and i / exp_elapsed or 0
local fmt = verbose and "%d\t%f\t(%d/s)\t(fill:%f)" or "%d\t%f"
aux.say(fmt, i, exp_elapsed, exp_rate, fill_elapsed)
end

View File

@ -0,0 +1,236 @@
/*
* Copyright (c) 2006 Maxim Yegorushkin <maxim.yegorushkin@gmail.com>
* All rights reserved.
*
* 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 _MIN_HEAP_H_
#define _MIN_HEAP_H_
#include <stdlib.h>
#include <err.h>
#include "timeout.h"
#include "bench.h"
#define min_heap_idx interval
typedef timeout_t min_heap_idx_t;
typedef struct min_heap
{
struct timeout** p;
unsigned n, a;
timeout_t curtime;
} min_heap_t;
static inline void min_heap_ctor(min_heap_t* s);
static inline void min_heap_dtor(min_heap_t* s);
static inline void min_heap_elem_init(struct timeout* e);
static inline int min_heap_elem_greater(struct timeout *a, struct timeout *b);
static inline int min_heap_empty(min_heap_t* s);
static inline unsigned min_heap_size(min_heap_t* s);
static inline struct timeout* min_heap_top(min_heap_t* s);
static inline int min_heap_reserve(min_heap_t* s, unsigned n);
static inline int min_heap_push(min_heap_t* s, struct timeout* e);
static inline struct timeout* min_heap_pop(min_heap_t* s);
static inline int min_heap_erase(min_heap_t* s, struct timeout* e);
static inline void min_heap_shift_up_(min_heap_t* s, unsigned hole_index, struct timeout* e);
static inline void min_heap_shift_down_(min_heap_t* s, unsigned hole_index, struct timeout* e);
int min_heap_elem_greater(struct timeout *a, struct timeout *b)
{
return a->expires > b->expires;
}
void min_heap_ctor(min_heap_t* s) { s->p = 0; s->n = 0; s->a = 0; }
void min_heap_dtor(min_heap_t* s) { if(s->p) free(s->p); }
void min_heap_elem_init(struct timeout* e) { e->min_heap_idx = -1; }
int min_heap_empty(min_heap_t* s) { return 0u == s->n; }
unsigned min_heap_size(min_heap_t* s) { return s->n; }
struct timeout* min_heap_top(min_heap_t* s) { return s->n ? *s->p : 0; }
int min_heap_push(min_heap_t* s, struct timeout* e)
{
if(min_heap_reserve(s, s->n + 1))
return -1;
min_heap_shift_up_(s, s->n++, e);
return 0;
}
struct timeout* min_heap_pop(min_heap_t* s)
{
if(s->n)
{
struct timeout* e = *s->p;
min_heap_shift_down_(s, 0u, s->p[--s->n]);
e->min_heap_idx = -1;
return e;
}
return 0;
}
int min_heap_erase(min_heap_t* s, struct timeout* e)
{
if(((min_heap_idx_t)-1) != e->min_heap_idx)
{
struct timeout *last = s->p[--s->n];
unsigned parent = (e->min_heap_idx - 1) / 2;
/* we replace e with the last element in the heap. We might need to
shift it upward if it is less than its parent, or downward if it is
greater than one or both its children. Since the children are known
to be less than the parent, it can't need to shift both up and
down. */
if (e->min_heap_idx > 0 && min_heap_elem_greater(s->p[parent], last))
min_heap_shift_up_(s, e->min_heap_idx, last);
else
min_heap_shift_down_(s, e->min_heap_idx, last);
e->min_heap_idx = -1;
return 0;
}
return -1;
}
int min_heap_reserve(min_heap_t* s, unsigned n)
{
if(s->a < n)
{
struct timeout** p;
unsigned a = s->a ? s->a * 2 : 8;
if(a < n)
a = n;
if(!(p = (struct timeout**)realloc(s->p, a * sizeof *p)))
return -1;
s->p = p;
s->a = a;
}
return 0;
}
void min_heap_shift_up_(min_heap_t* s, unsigned hole_index, struct timeout* e)
{
unsigned parent = (hole_index - 1) / 2;
while(hole_index && min_heap_elem_greater(s->p[parent], e))
{
(s->p[hole_index] = s->p[parent])->min_heap_idx = hole_index;
hole_index = parent;
parent = (hole_index - 1) / 2;
}
(s->p[hole_index] = e)->min_heap_idx = hole_index;
}
void min_heap_shift_down_(min_heap_t* s, unsigned hole_index, struct timeout* e)
{
unsigned min_child = 2 * (hole_index + 1);
while(min_child <= s->n)
{
min_child -= min_child == s->n || min_heap_elem_greater(s->p[min_child], s->p[min_child - 1]);
if(!(min_heap_elem_greater(e, s->p[min_child])))
break;
(s->p[hole_index] = s->p[min_child])->min_heap_idx = hole_index;
hole_index = min_child;
min_child = 2 * (hole_index + 1);
}
min_heap_shift_up_(s, hole_index, e);
}
#endif /* _MIN_HEAP_H_ */
static void *init(struct timeout *timeout, size_t count, int verbose) {
min_heap_t *H;
size_t i;
H = calloc(1, sizeof *H);
min_heap_ctor(H);
if (0 != min_heap_reserve(H, count))
err(1, "realloc");
for (i = 0; i < count; i++) {
min_heap_elem_init(&timeout[i]);
}
return H;
} /* init() */
static void add(void *ctx, struct timeout *to, timeout_t expires) {
min_heap_t *H = ctx;
min_heap_erase(H, to);
to->expires = H->curtime + expires;
if (0 != min_heap_push(H, to))
err(1, "realloc");
} /* add() */
static void del(void *ctx, struct timeout *to) {
min_heap_erase(ctx, to);
} /* del() */
static struct timeout *get(void *ctx) {
min_heap_t *H = ctx;
struct timeout *to;
if ((to = min_heap_top(H)) && to->expires <= H->curtime)
return min_heap_pop(H);
return NULL;
} /* get() */
static void update(void *ctx, timeout_t ts) {
min_heap_t *H = ctx;
H->curtime = ts;
} /* update() */
static void check(void *ctx) {
return;
} /* check() */
static int empty(void *ctx) {
min_heap_t *H = ctx;
return (NULL == min_heap_top(H));
} /* empty() */
static void destroy(void *H) {
free(H);
return;
} /* destroy() */
const struct benchops benchops = {
.init = &init,
.add = &add,
.del = &del,
.get = &get,
.update = &update,
.check = &check,
.empty = &empty,
.destroy = &destroy,
};

View File

@ -0,0 +1,425 @@
/* ==========================================================================
* llrb.h - Iterative Left-leaning Red-Black Tree.
* --------------------------------------------------------------------------
* Copyright (c) 2011, 2013 William Ahern <william@25thandClement.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to permit
* persons to whom the Software is furnished to do so, subject to the
* following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
* NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
* USE OR OTHER DEALINGS IN THE SOFTWARE.
* --------------------------------------------------------------------------
* CREDITS:
* o Algorithm courtesy of Robert Sedgewick, "Left-leaning Red-Black
* Trees" (September 2008); and Robert Sedgewick and Kevin Wayne,
* Algorithms (4th ed. 2011).
*
* Sedgewick touts the simplicity of the recursive implementation,
* but at least for the 2-3 tree variant the iterative approach is
* almost line-for-line identical. The magic of C pointers helps;
* it'd be uglier with Java.
*
* A couple of missing NULL checks were added to Sedgewick's deletion
* example, and insert was optimized to short-circuit rotations when
* walking up the tree.
*
* o Code implemented in the fashion of Niels Provos' excellent *BSD
* sys/tree.h pre-processor library.
*
* Regarding relative performance, I've refrained from sharing my own
* benchmarks. Differences in run-time speed were too correlated to
* compiler options and other external factors.
*
* Provos' delete implementation doesn't need to start at the root of
* the tree. However, RB_REMOVE must be passed the actual node to be
* removed. LLRB_REMOVE merely requires a key, much like
* RB_FIND/LLRB_FIND.
* ==========================================================================
*/
#ifndef LLRB_H
#define LLRB_H
#define LLRB_VENDOR "william@25thandClement.com"
#define LLRB_VERSION 0x20130925
#ifndef LLRB_STATIC
#ifdef __GNUC__
#define LLRB_STATIC __attribute__((__unused__)) static
#else
#define LLRB_STATIC static
#endif
#endif
#define LLRB_HEAD(name, type) \
struct name { struct type *rbh_root; }
#define LLRB_INITIALIZER(root) { 0 }
#define LLRB_INIT(root) do { (root)->rbh_root = 0; } while (0)
#define LLRB_BLACK 0
#define LLRB_RED 1
#define LLRB_ENTRY(type) \
struct { struct type *rbe_left, *rbe_right, *rbe_parent; _Bool rbe_color; }
#define LLRB_LEFT(elm, field) (elm)->field.rbe_left
#define LLRB_RIGHT(elm, field) (elm)->field.rbe_right
#define LLRB_PARENT(elm, field) (elm)->field.rbe_parent
#define LLRB_EDGE(head, elm, field) (((elm) == LLRB_ROOT(head))? &LLRB_ROOT(head) : ((elm) == LLRB_LEFT(LLRB_PARENT((elm), field), field))? &LLRB_LEFT(LLRB_PARENT((elm), field), field) : &LLRB_RIGHT(LLRB_PARENT((elm), field), field))
#define LLRB_COLOR(elm, field) (elm)->field.rbe_color
#define LLRB_ROOT(head) (head)->rbh_root
#define LLRB_EMPTY(head) ((head)->rbh_root == 0)
#define LLRB_ISRED(elm, field) ((elm) && LLRB_COLOR((elm), field) == LLRB_RED)
#define LLRB_PROTOTYPE(name, type, field, cmp) \
LLRB_PROTOTYPE_INTERNAL(name, type, field, cmp,)
#define LLRB_PROTOTYPE_STATIC(name, type, field, cmp) \
LLRB_PROTOTYPE_INTERNAL(name, type, field, cmp, LLRB_STATIC)
#define LLRB_PROTOTYPE_INTERNAL(name, type, field, cmp, attr) \
attr struct type *name##_LLRB_INSERT(struct name *, struct type *); \
attr struct type *name##_LLRB_DELETE(struct name *, struct type *); \
attr struct type *name##_LLRB_FIND(struct name *, struct type *); \
attr struct type *name##_LLRB_MIN(struct type *); \
attr struct type *name##_LLRB_MAX(struct type *); \
attr struct type *name##_LLRB_NEXT(struct type *);
#define LLRB_GENERATE(name, type, field, cmp) \
LLRB_GENERATE_INTERNAL(name, type, field, cmp,)
#define LLRB_GENERATE_STATIC(name, type, field, cmp) \
LLRB_GENERATE_INTERNAL(name, type, field, cmp, LLRB_STATIC)
#define LLRB_GENERATE_INTERNAL(name, type, field, cmp, attr) \
static inline void name##_LLRB_ROTL(struct type **pivot) { \
struct type *a = *pivot; \
struct type *b = LLRB_RIGHT(a, field); \
if ((LLRB_RIGHT(a, field) = LLRB_LEFT(b, field))) \
LLRB_PARENT(LLRB_RIGHT(a, field), field) = a; \
LLRB_LEFT(b, field) = a; \
LLRB_COLOR(b, field) = LLRB_COLOR(a, field); \
LLRB_COLOR(a, field) = LLRB_RED; \
LLRB_PARENT(b, field) = LLRB_PARENT(a, field); \
LLRB_PARENT(a, field) = b; \
*pivot = b; \
} \
static inline void name##_LLRB_ROTR(struct type **pivot) { \
struct type *b = *pivot; \
struct type *a = LLRB_LEFT(b, field); \
if ((LLRB_LEFT(b, field) = LLRB_RIGHT(a, field))) \
LLRB_PARENT(LLRB_LEFT(b, field), field) = b; \
LLRB_RIGHT(a, field) = b; \
LLRB_COLOR(a, field) = LLRB_COLOR(b, field); \
LLRB_COLOR(b, field) = LLRB_RED; \
LLRB_PARENT(a, field) = LLRB_PARENT(b, field); \
LLRB_PARENT(b, field) = a; \
*pivot = a; \
} \
static inline void name##_LLRB_FLIP(struct type *root) { \
LLRB_COLOR(root, field) = !LLRB_COLOR(root, field); \
LLRB_COLOR(LLRB_LEFT(root, field), field) = !LLRB_COLOR(LLRB_LEFT(root, field), field); \
LLRB_COLOR(LLRB_RIGHT(root, field), field) = !LLRB_COLOR(LLRB_RIGHT(root, field), field); \
} \
static inline void name##_LLRB_FIXUP(struct type **root) { \
if (LLRB_ISRED(LLRB_RIGHT(*root, field), field) && !LLRB_ISRED(LLRB_LEFT(*root, field), field)) \
name##_LLRB_ROTL(root); \
if (LLRB_ISRED(LLRB_LEFT(*root, field), field) && LLRB_ISRED(LLRB_LEFT(LLRB_LEFT(*root, field), field), field)) \
name##_LLRB_ROTR(root); \
if (LLRB_ISRED(LLRB_LEFT(*root, field), field) && LLRB_ISRED(LLRB_RIGHT(*root, field), field)) \
name##_LLRB_FLIP(*root); \
} \
attr struct type *name##_LLRB_INSERT(struct name *head, struct type *elm) { \
struct type **root = &LLRB_ROOT(head); \
struct type *parent = 0; \
while (*root) { \
int comp = (cmp)((elm), (*root)); \
parent = *root; \
if (comp < 0) \
root = &LLRB_LEFT(*root, field); \
else if (comp > 0) \
root = &LLRB_RIGHT(*root, field); \
else \
return *root; \
} \
LLRB_LEFT((elm), field) = 0; \
LLRB_RIGHT((elm), field) = 0; \
LLRB_COLOR((elm), field) = LLRB_RED; \
LLRB_PARENT((elm), field) = parent; \
*root = (elm); \
while (parent && (LLRB_ISRED(LLRB_LEFT(parent, field), field) || LLRB_ISRED(LLRB_RIGHT(parent, field), field))) { \
root = LLRB_EDGE(head, parent, field); \
parent = LLRB_PARENT(parent, field); \
name##_LLRB_FIXUP(root); \
} \
LLRB_COLOR(LLRB_ROOT(head), field) = LLRB_BLACK; \
return 0; \
} \
static inline void name##_LLRB_MOVL(struct type **pivot) { \
name##_LLRB_FLIP(*pivot); \
if (LLRB_ISRED(LLRB_LEFT(LLRB_RIGHT(*pivot, field), field), field)) { \
name##_LLRB_ROTR(&LLRB_RIGHT(*pivot, field)); \
name##_LLRB_ROTL(pivot); \
name##_LLRB_FLIP(*pivot); \
} \
} \
static inline void name##_LLRB_MOVR(struct type **pivot) { \
name##_LLRB_FLIP(*pivot); \
if (LLRB_ISRED(LLRB_LEFT(LLRB_LEFT(*pivot, field), field), field)) { \
name##_LLRB_ROTR(pivot); \
name##_LLRB_FLIP(*pivot); \
} \
} \
static inline struct type *name##_DELETEMIN(struct name *head, struct type **root) { \
struct type **pivot = root, *deleted, *parent; \
while (LLRB_LEFT(*pivot, field)) { \
if (!LLRB_ISRED(LLRB_LEFT(*pivot, field), field) && !LLRB_ISRED(LLRB_LEFT(LLRB_LEFT(*pivot, field), field), field)) \
name##_LLRB_MOVL(pivot); \
pivot = &LLRB_LEFT(*pivot, field); \
} \
deleted = *pivot; \
parent = LLRB_PARENT(*pivot, field); \
*pivot = 0; \
while (root != pivot) { \
pivot = LLRB_EDGE(head, parent, field); \
parent = LLRB_PARENT(parent, field); \
name##_LLRB_FIXUP(pivot); \
} \
return deleted; \
} \
attr struct type *name##_LLRB_DELETE(struct name *head, struct type *elm) { \
struct type **root = &LLRB_ROOT(head), *parent = 0, *deleted = 0; \
int comp; \
while (*root) { \
parent = LLRB_PARENT(*root, field); \
comp = (cmp)(elm, *root); \
if (comp < 0) { \
if (LLRB_LEFT(*root, field) && !LLRB_ISRED(LLRB_LEFT(*root, field), field) && !LLRB_ISRED(LLRB_LEFT(LLRB_LEFT(*root, field), field), field)) \
name##_LLRB_MOVL(root); \
root = &LLRB_LEFT(*root, field); \
} else { \
if (LLRB_ISRED(LLRB_LEFT(*root, field), field)) { \
name##_LLRB_ROTR(root); \
comp = (cmp)(elm, *root); \
} \
if (!comp && !LLRB_RIGHT(*root, field)) { \
deleted = *root; \
*root = 0; \
break; \
} \
if (LLRB_RIGHT(*root, field) && !LLRB_ISRED(LLRB_RIGHT(*root, field), field) && !LLRB_ISRED(LLRB_LEFT(LLRB_RIGHT(*root, field), field), field)) { \
name##_LLRB_MOVR(root); \
comp = (cmp)(elm, *root); \
} \
if (!comp) { \
struct type *orphan = name##_DELETEMIN(head, &LLRB_RIGHT(*root, field)); \
LLRB_COLOR(orphan, field) = LLRB_COLOR(*root, field); \
LLRB_PARENT(orphan, field) = LLRB_PARENT(*root, field); \
if ((LLRB_RIGHT(orphan, field) = LLRB_RIGHT(*root, field))) \
LLRB_PARENT(LLRB_RIGHT(orphan, field), field) = orphan; \
if ((LLRB_LEFT(orphan, field) = LLRB_LEFT(*root, field))) \
LLRB_PARENT(LLRB_LEFT(orphan, field), field) = orphan; \
deleted = *root; \
*root = orphan; \
parent = *root; \
break; \
} else \
root = &LLRB_RIGHT(*root, field); \
} \
} \
while (parent) { \
root = LLRB_EDGE(head, parent, field); \
parent = LLRB_PARENT(parent, field); \
name##_LLRB_FIXUP(root); \
} \
if (LLRB_ROOT(head)) \
LLRB_COLOR(LLRB_ROOT(head), field) = LLRB_BLACK; \
return deleted; \
} \
attr struct type *name##_LLRB_FIND(struct name *head, struct type *key) { \
struct type *elm = LLRB_ROOT(head); \
while (elm) { \
int comp = (cmp)(key, elm); \
if (comp < 0) \
elm = LLRB_LEFT(elm, field); \
else if (comp > 0) \
elm = LLRB_RIGHT(elm, field); \
else \
return elm; \
} \
return 0; \
} \
attr struct type *name##_LLRB_MIN(struct type *elm) { \
while (elm && LLRB_LEFT(elm, field)) \
elm = LLRB_LEFT(elm, field); \
return elm; \
} \
attr struct type *name##_LLRB_MAX(struct type *elm) { \
while (elm && LLRB_RIGHT(elm, field)) \
elm = LLRB_RIGHT(elm, field); \
return elm; \
} \
attr struct type *name##_LLRB_NEXT(struct type *elm) { \
if (LLRB_RIGHT(elm, field)) { \
return name##_LLRB_MIN(LLRB_RIGHT(elm, field)); \
} else if (LLRB_PARENT(elm, field)) { \
if (elm == LLRB_LEFT(LLRB_PARENT(elm, field), field)) \
return LLRB_PARENT(elm, field); \
while (LLRB_PARENT(elm, field) && elm == LLRB_RIGHT(LLRB_PARENT(elm, field), field)) \
elm = LLRB_PARENT(elm, field); \
return LLRB_PARENT(elm, field); \
} else return 0; \
}
#define LLRB_INSERT(name, head, elm) name##_LLRB_INSERT((head), (elm))
#define LLRB_DELETE(name, head, elm) name##_LLRB_DELETE((head), (elm))
#define LLRB_REMOVE(name, head, elm) name##_LLRB_DELETE((head), (elm))
#define LLRB_FIND(name, head, elm) name##_LLRB_FIND((head), (elm))
#define LLRB_MIN(name, head) name##_LLRB_MIN(LLRB_ROOT((head)))
#define LLRB_MAX(name, head) name##_LLRB_MAX(LLRB_ROOT((head)))
#define LLRB_NEXT(name, head, elm) name##_LLRB_NEXT((elm))
#define LLRB_FOREACH(elm, name, head) \
for ((elm) = LLRB_MIN(name, head); (elm); (elm) = name##_LLRB_NEXT((elm)))
#endif /* LLRB_H */
#include <stdlib.h>
#include "timeout.h"
#include "bench.h"
struct rbtimeout {
timeout_t expires;
int pending;
LLRB_ENTRY(rbtimeout) rbe;
};
struct rbtimeouts {
timeout_t curtime;
LLRB_HEAD(tree, rbtimeout) tree;
};
static int timeoutcmp(struct rbtimeout *a, struct rbtimeout *b) {
if (a->expires < b->expires) {
return -1;
} else if (a->expires > b->expires) {
return 1;
} else if (a < b) {
return -1;
} else if (a > b) {
return 1;
} else {
return 0;
}
} /* timeoutcmp() */
LLRB_GENERATE_STATIC(tree, rbtimeout, rbe, timeoutcmp)
static void *init(struct timeout *timeout, size_t count, int verbose) {
struct rbtimeouts *T;
size_t i;
T = malloc(sizeof *T);
T->curtime = 0;
LLRB_INIT(&T->tree);
for (i = 0; i < count; i++) {
struct rbtimeout *to = (void *)&timeout[i];
to->expires = 0;
to->pending = 0;
}
return T;
} /* init() */
static void add(void *ctx, struct timeout *_to, timeout_t expires) {
struct rbtimeouts *T = ctx;
struct rbtimeout *to = (void *)_to;
if (to->pending)
LLRB_REMOVE(tree, &T->tree, to);
to->expires = T->curtime + expires;
LLRB_INSERT(tree, &T->tree, to);
to->pending = 1;
} /* add() */
static void del(void *ctx, struct timeout *_to) {
struct rbtimeouts *T = ctx;
struct rbtimeout *to = (void *)_to;
LLRB_REMOVE(tree, &T->tree, to);
to->pending = 0;
to->expires = 0;
} /* del() */
static struct timeout *get(void *ctx) {
struct rbtimeouts *T = ctx;
struct rbtimeout *to;
if ((to = LLRB_MIN(tree, &T->tree)) && to->expires <= T->curtime) {
LLRB_REMOVE(tree, &T->tree, to);
to->pending = 0;
to->expires = 0;
return (void *)to;
}
return NULL;
} /* get() */
static void update(void *ctx, timeout_t ts) {
struct rbtimeouts *T = ctx;
T->curtime = ts;
} /* update() */
static void check(void *ctx) {
return;
} /* check() */
static int empty(void *ctx) {
struct rbtimeouts *T = ctx;
return LLRB_EMPTY(&T->tree);
} /* empty() */
static void destroy(void *ctx) {
free(ctx);
return;
} /* destroy() */
const struct benchops benchops = {
.init = &init,
.add = &add,
.del = &del,
.get = &get,
.update = &update,
.check = &check,
.empty = &empty,
.destroy = &destroy,
};

View File

@ -0,0 +1,81 @@
#include <stdlib.h>
#define TIMEOUT_PUBLIC static
#include "timeout.h"
#include "timeout.c"
#include "bench.h"
static void *init(struct timeout *timeout, size_t count, int verbose) {
struct timeouts *T;
size_t i;
int error;
T = timeouts_open(TIMEOUT_mHZ, &error);
for (i = 0; i < count; i++) {
timeout_init(&timeout[i], 0);
}
#if TIMEOUT_DEBUG - 0
timeout_debug = verbose;
#endif
return T;
} /* init() */
static void add(void *T, struct timeout *to, timeout_t expires) {
timeouts_add(T, to, expires);
} /* add() */
static void del(void *T, struct timeout *to) {
timeouts_del(T, to);
} /* del() */
static struct timeout *get(void *T) {
return timeouts_get(T);
} /* get() */
static void update(void *T, timeout_t ts) {
timeouts_update(T, ts);
} /* update() */
static void (check)(void *T) {
if (!timeouts_check(T, stderr))
_Exit(1);
} /* check() */
static int empty(void *T) {
return !(timeouts_pending(T) || timeouts_expired(T));
} /* empty() */
static struct timeout *next(void *T, struct timeouts_it *it) {
return timeouts_next(T, it);
} /* next() */
static void destroy(void *T) {
timeouts_close(T);
} /* destroy() */
const struct benchops benchops = {
.init = &init,
.add = &add,
.del = &del,
.get = &get,
.update = &update,
.check = &check,
.empty = &empty,
.next = &next,
.destroy = &destroy
};

View File

@ -0,0 +1,293 @@
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#include <unistd.h>
#include <dlfcn.h>
#if __APPLE__
#include <mach/mach_time.h>
#endif
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
#include "timeout.h"
#include "bench.h"
#if LUA_VERSION_NUM < 502
static int lua_absindex(lua_State *L, int idx) {
return (idx > 0 || idx <= LUA_REGISTRYINDEX)? idx : lua_gettop(L) + idx + 1;
} /* lua_absindex() */
static void luaL_setfuncs(lua_State *L, const luaL_Reg *l, int nup) {
int i, t = lua_absindex(L, -1 - nup);
for (; l->name; l++) {
for (i = 0; i < nup; i++)
lua_pushvalue(L, -nup);
lua_pushcclosure(L, l->func, nup);
lua_setfield(L, t, l->name);
}
lua_pop(L, nup);
} /* luaL_setfuncs() */
#define luaL_newlibtable(L, l) \
lua_createtable(L, 0, (sizeof (l) / sizeof *(l)) - 1)
#define luaL_newlib(L, l) \
(luaL_newlibtable((L), (l)), luaL_setfuncs((L), (l), 0))
#endif
#ifndef MAX
#define MAX(a, b) (((a) > (b))? (a) : (b))
#endif
struct bench {
const char *path;
void *solib;
size_t count;
timeout_t timeout_max;
int verbose;
void *state;
struct timeout *timeout;
struct benchops ops;
timeout_t curtime;
}; /* struct bench */
#if __APPLE__
static mach_timebase_info_data_t timebase;
#endif
static int long long monotime(void) {
#if __APPLE__
unsigned long long abt;
abt = mach_absolute_time();
abt = abt * timebase.numer / timebase.denom;
return abt / 1000LL;
#else
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (ts.tv_sec * 1000000L) + (ts.tv_nsec / 1000L);
#endif
} /* monotime() */
static int bench_clock(lua_State *L) {
lua_pushnumber(L, (double)monotime() / 1000000L);
return 1;
} /* bench_clock() */
static int bench_new(lua_State *L) {
const char *path = luaL_checkstring(L, 1);
size_t count = luaL_optinteger(L, 2, 1000000);
timeout_t timeout_max = luaL_optinteger(L, 3, 300 * 1000000L);
int verbose = (lua_isnone(L, 4))? 0 : lua_toboolean(L, 4);
struct bench *B;
struct benchops *ops;
B = lua_newuserdata(L, sizeof *B);
memset(B, 0, sizeof *B);
luaL_getmetatable(L, "BENCH*");
lua_setmetatable(L, -2);
B->count = count;
B->timeout_max = timeout_max;
B->verbose = verbose;
if (!(B->timeout = calloc(count, sizeof *B->timeout)))
return luaL_error(L, "%s", strerror(errno));
if (!(B->solib = dlopen(path, RTLD_NOW|RTLD_LOCAL)))
return luaL_error(L, "%s: %s", path, dlerror());
if (!(ops = dlsym(B->solib, "benchops")))
return luaL_error(L, "%s: %s", path, dlerror());
B->ops = *ops;
B->state = B->ops.init(B->timeout, B->count, B->verbose);
return 1;
} /* bench_new() */
static int bench_add(lua_State *L) {
struct bench *B = lua_touserdata(L, 1);
unsigned i;
timeout_t t;
i = (lua_isnoneornil(L, 2))? random() % B->count : (unsigned)luaL_checkinteger(L, 2);
t = (lua_isnoneornil(L, 3))? random() % B->timeout_max : (unsigned)luaL_checkinteger(L, 3);
B->ops.add(B->state, &B->timeout[i], t);
return 0;
} /* bench_add() */
static int bench_del(lua_State *L) {
struct bench *B = lua_touserdata(L, 1);
size_t i = luaL_optinteger(L, 2, random() % B->count);
size_t j = luaL_optinteger(L, 3, i);
while (i <= j && i < B->count) {
B->ops.del(B->state, &B->timeout[i]);
++i;
}
return 0;
} /* bench_del() */
static int bench_fill(lua_State *L) {
struct bench *B = lua_touserdata(L, 1);
size_t count = luaL_optinteger(L, 2, B->count);
long timeout_inc = luaL_optinteger(L, 3, -1), timeout_max = 0, timeout;
size_t i;
if (timeout_inc < 0) {
for (i = 0; i < count; i++) {
timeout = random() % B->timeout_max;
B->ops.add(B->state, &B->timeout[i], timeout);
timeout_max = MAX(timeout, timeout_max);
}
} else {
for (i = 0; i < count; i++) {
timeout = timeout_inc + i;
B->ops.add(B->state, &B->timeout[i], timeout_inc + i);
timeout_max = MAX(timeout, timeout_max);
}
}
lua_pushinteger(L, (lua_Integer)count);
lua_pushinteger(L, (lua_Integer)timeout_max);
return 2;
} /* bench_fill() */
static int bench_expire(lua_State *L) {
struct bench *B = lua_touserdata(L, 1);
unsigned count = luaL_optinteger(L, 2, B->count);
unsigned step = luaL_optinteger(L, 3, 300000);
size_t i = 0;
while (i < count && !B->ops.empty(B->state)) {
B->curtime += step;
B->ops.update(B->state, B->curtime);
while (B->ops.get(B->state))
i++;
}
lua_pushinteger(L, (lua_Integer)i);
return 1;
} /* bench_expire() */
static int bench_empty(lua_State *L) {
struct bench *B = lua_touserdata(L, 1);
lua_pushboolean(L, B->ops.empty(B->state));
return 1;
} /* bench_empty() */
static int bench__next(lua_State *L) {
struct bench *B = lua_touserdata(L, lua_upvalueindex(1));
struct timeouts_it *it = lua_touserdata(L, lua_upvalueindex(2));
struct timeout *to;
if (!B->ops.next || !(to = B->ops.next(B->state, it)))
return 0;
lua_pushinteger(L, luaL_optinteger(L, 2, 0) + 1);
lua_newtable(L);
lua_pushinteger(L, to->expires);
lua_setfield(L, -2, "expires");
return 2;
} /* bench__next() */
static int bench__pairs(lua_State *L) {
struct timeouts_it *it;
lua_settop(L, 1);
it = lua_newuserdata(L, sizeof *it);
TIMEOUTS_IT_INIT(it, TIMEOUTS_ALL);
lua_pushcclosure(L, &bench__next, 2);
lua_pushvalue(L, 1);
lua_pushinteger(L, 0);
return 3;
} /* bench__pairs() */
static int bench__gc(lua_State *L) {
struct bench *B = lua_touserdata(L, 1);
if (B->state) {
B->ops.destroy(B->state);
B->state = NULL;
}
return 0;
} /* bench__gc() */
static const luaL_Reg bench_methods[] = {
{ "add", &bench_add },
{ "del", &bench_del },
{ "fill", &bench_fill },
{ "expire", &bench_expire },
{ "empty", &bench_empty },
{ "close", &bench__gc },
{ NULL, NULL }
};
static const luaL_Reg bench_metatable[] = {
{ "__pairs", &bench__pairs },
{ "__gc", &bench__gc },
{ NULL, NULL }
};
static const luaL_Reg bench_globals[] = {
{ "new", &bench_new },
{ "clock", &bench_clock },
{ NULL, NULL }
};
int luaopen_bench(lua_State *L) {
#if __APPLE__
mach_timebase_info(&timebase);
#endif
if (luaL_newmetatable(L, "BENCH*")) {
luaL_setfuncs(L, bench_metatable, 0);
luaL_newlib(L, bench_methods);
lua_setfield(L, -2, "__index");
}
luaL_newlib(L, bench_globals);
return 1;
} /* luaopen_bench() */

View File

@ -0,0 +1,11 @@
struct benchops {
void *(*init)(struct timeout *, size_t, int);
void (*add)(void *, struct timeout *, timeout_t);
void (*del)(void *, struct timeout *);
struct timeout *(*get)(void *);
void (*update)(void *, timeout_t);
void (*check)(void *);
int (*empty)(void *);
struct timeout *(*next)(void *, struct timeouts_it *);
void (*destroy)(void *);
}; /* struct benchops() */

View File

@ -0,0 +1,19 @@
set terminal postscript color
set key top left
set xlabel "Number of timeouts"
set ylabel "Time\n(microseconds)"
#set logscale x
set title "Time spent installing timeouts" font ",20"
plot 'heap-add.dat' using 1:($2*1000000) title "min-heap" with lines ls 1 lw 3 lc "red", \
'wheel-add.dat' using 1:($2*1000000) title "hierarchical wheel" with lines ls 1 lw 3 lc "forest-green"
set title "Time spent deleting timeouts" font ",20"
plot 'heap-del.dat' using 1:($2*1000000) title "min-heap" with lines ls 1 lw 3 lc "red", \
'wheel-del.dat' using 1:($2*1000000) title "hierarchical wheel" with lines ls 1 lw 3 lc "forest-green"
set title "Time spent expiring timeouts\n(by iteratively updating clock ~1000 times)" font ",20"
plot 'heap-expire.dat' using 1:($2*1000000) title "min-heap" with lines ls 1 lw 3 lc "red", \
'wheel-expire.dat' using 1:($2*1000000) title "hierarchical wheel" with lines ls 1 lw 3 lc "forest-green"

View File

@ -0,0 +1,20 @@
$(LUA_APIS:%=$(top_builddir)/lua/%/timeout.so): $(top_srcdir)/lua/timeout-lua.c $(top_srcdir)/timeout.h $(top_srcdir)/timeout.c
mkdir -p $(@D)
@$(SHRC); echo_cmd $(CC) -o $@ $(top_srcdir)/lua/timeout-lua.c -I$(top_srcdir) -DWHEEL_BIT=$(WHEEL_BIT) -DWHEEL_NUM=$(WHEEL_NUM) $(LUA53_CPPFLAGS) $(ALL_CPPFLAGS) $(ALL_CFLAGS) $(ALL_SOFLAGS) $(ALL_LDFLAGS) $(ALL_LIBS)
$(top_builddir)/lua/5.1/timeouts.so: $(top_builddir)/lua/5.1/timeout.so
$(top_builddir)/lua/5.2/timeouts.so: $(top_builddir)/lua/5.2/timeout.so
$(top_builddir)/lua/5.3/timeouts.so: $(top_builddir)/lua/5.3/timeout.so
$(LUA_APIS:%=$(top_builddir)/lua/%/timeouts.so):
cd $(@D) && ln -fs timeout.so timeouts.so
lua-5.1: $(top_builddir)/lua/5.1/timeout.so $(top_builddir)/lua/5.1/timeouts.so
lua-5.2: $(top_builddir)/lua/5.2/timeout.so $(top_builddir)/lua/5.2/timeouts.so
lua-5.3: $(top_builddir)/lua/5.3/timeout.so $(top_builddir)/lua/5.3/timeouts.so
lua-clean:
$(RM) -r $(top_builddir)/lua/5.?
clean: lua-clean

View File

@ -0,0 +1,396 @@
#include <assert.h>
#include <string.h>
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
#if LUA_VERSION_NUM != 503
#error only Lua 5.3 supported
#endif
#define TIMEOUT_PUBLIC static
#include "timeout.h"
#include "timeout.c"
#define TIMEOUT_METANAME "struct timeout"
#define TIMEOUTS_METANAME "struct timeouts*"
static struct timeout *
to_checkudata(lua_State *L, int index)
{
return luaL_checkudata(L, index, TIMEOUT_METANAME);
}
static struct timeouts *
tos_checkudata(lua_State *L, int index)
{
return *(struct timeouts **)luaL_checkudata(L, index, TIMEOUTS_METANAME);
}
static void
tos_bind(lua_State *L, int tos_index, int to_index)
{
lua_getuservalue(L, tos_index);
lua_pushlightuserdata(L, to_checkudata(L, to_index));
lua_pushvalue(L, to_index);
lua_rawset(L, -3);
lua_pop(L, 1);
}
static void
tos_unbind(lua_State *L, int tos_index, int to_index)
{
lua_getuservalue(L, tos_index);
lua_pushlightuserdata(L, to_checkudata(L, to_index));
lua_pushnil(L);
lua_rawset(L, -3);
lua_pop(L, 1);
}
static int
to__index(lua_State *L)
{
struct timeout *to = to_checkudata(L, 1);
if (lua_type(L, 2 == LUA_TSTRING)) {
const char *key = lua_tostring(L, 2);
if (!strcmp(key, "flags")) {
lua_pushinteger(L, to->flags);
return 1;
} else if (!strcmp(key, "expires")) {
lua_pushinteger(L, to->expires);
return 1;
}
}
if (LUA_TNIL != lua_getuservalue(L, 1)) {
lua_pushvalue(L, 2);
if (LUA_TNIL != lua_rawget(L, -2))
return 1;
}
lua_pushvalue(L, 2);
if (LUA_TNIL != lua_rawget(L, lua_upvalueindex(1)))
return 1;
return 0;
}
static int
to__newindex(lua_State *L)
{
if (LUA_TNIL == lua_getuservalue(L, 1)) {
lua_newtable(L);
lua_pushvalue(L, -1);
lua_setuservalue(L, 1);
}
lua_pushvalue(L, 2);
lua_pushvalue(L, 3);
lua_rawset(L, -3);
return 0;
}
static int
to__gc(lua_State *L)
{
struct timeout *to = to_checkudata(L, 1);
/*
* NB: On script exit it's possible for a timeout to still be
* associated with a timeouts object, particularly when the timeouts
* object was created first.
*/
timeout_del(to);
return 0;
}
static int
to_new(lua_State *L)
{
int flags = luaL_optinteger(L, 1, 0);
struct timeout *to;
to = lua_newuserdata(L, sizeof *to);
timeout_init(to, flags);
luaL_setmetatable(L, TIMEOUT_METANAME);
return 1;
}
static const luaL_Reg to_methods[] = {
{ NULL, NULL },
};
static const luaL_Reg to_metatable[] = {
{ "__index", &to__index },
{ "__newindex", &to__newindex },
{ "__gc", &to__gc },
{ NULL, NULL },
};
static const luaL_Reg to_globals[] = {
{ "new", &to_new },
{ NULL, NULL },
};
static void
to_newmetatable(lua_State *L)
{
if (luaL_newmetatable(L, TIMEOUT_METANAME)) {
/*
* fill metamethod table, capturing the methods table as an
* upvalue for use by __index metamethod
*/
luaL_newlib(L, to_methods);
luaL_setfuncs(L, to_metatable, 1);
}
}
int
luaopen_timeout(lua_State *L)
{
to_newmetatable(L);
luaL_newlib(L, to_globals);
lua_pushinteger(L, TIMEOUT_INT);
lua_setfield(L, -2, "INT");
lua_pushinteger(L, TIMEOUT_ABS);
lua_setfield(L, -2, "ABS");
return 1;
}
static int
tos_update(lua_State *L)
{
struct timeouts *T = tos_checkudata(L, 1);
lua_Number n = luaL_checknumber(L, 2);
timeouts_update(T, timeouts_f2i(T, n));
lua_pushvalue(L, 1);
return 1;
}
static int
tos_step(lua_State *L)
{
struct timeouts *T = tos_checkudata(L, 1);
lua_Number n = luaL_checknumber(L, 2);
timeouts_step(T, timeouts_f2i(T, n));
lua_pushvalue(L, 1);
return 1;
}
static int
tos_timeout(lua_State *L)
{
struct timeouts *T = tos_checkudata(L, 1);
lua_pushnumber(L, timeouts_i2f(T, timeouts_timeout(T)));
return 1;
}
static int
tos_add(lua_State *L)
{
struct timeouts *T = tos_checkudata(L, 1);
struct timeout *to = to_checkudata(L, 2);
lua_Number timeout = luaL_checknumber(L, 3);
tos_bind(L, 1, 2);
timeouts_addf(T, to, timeout);
return lua_pushvalue(L, 1), 1;
}
static int
tos_del(lua_State *L)
{
struct timeouts *T = tos_checkudata(L, 1);
struct timeout *to = to_checkudata(L, 2);
timeouts_del(T, to);
tos_unbind(L, 1, 2);
return lua_pushvalue(L, 1), 1;
}
static int
tos_get(lua_State *L)
{
struct timeouts *T = tos_checkudata(L, 1);
struct timeout *to;
if (!(to = timeouts_get(T)))
return 0;
lua_getuservalue(L, 1);
lua_rawgetp(L, -1, to);
if (!timeout_pending(to))
tos_unbind(L, 1, lua_absindex(L, -1));
return 1;
}
static int
tos_pending(lua_State *L)
{
struct timeouts *T = tos_checkudata(L, 1);
lua_pushboolean(L, timeouts_pending(T));
return 1;
}
static int
tos_expired(lua_State *L)
{
struct timeouts *T = tos_checkudata(L, 1);
lua_pushboolean(L, timeouts_expired(T));
return 1;
}
static int
tos_check(lua_State *L)
{
struct timeouts *T = tos_checkudata(L, 1);
lua_pushboolean(L, timeouts_check(T, NULL));
return 1;
}
static int
tos__next(lua_State *L)
{
struct timeouts *T = tos_checkudata(L, lua_upvalueindex(1));
struct timeouts_it *it = lua_touserdata(L, lua_upvalueindex(2));
struct timeout *to;
if (!(to = timeouts_next(T, it)))
return 0;
lua_getuservalue(L, lua_upvalueindex(1));
lua_rawgetp(L, -1, to);
return 1;
}
static int
tos_timeouts(lua_State *L)
{
int flags = luaL_checkinteger(L, 2);
struct timeouts_it *it;
tos_checkudata(L, 1);
lua_pushvalue(L, 1);
it = lua_newuserdata(L, sizeof *it);
TIMEOUTS_IT_INIT(it, flags);
lua_pushcclosure(L, &tos__next, 2);
return 1;
}
static int
tos__gc(lua_State *L)
{
struct timeouts **tos = luaL_checkudata(L, 1, TIMEOUTS_METANAME);
struct timeout *to;
TIMEOUTS_FOREACH(to, *tos, TIMEOUTS_ALL) {
timeouts_del(*tos, to);
}
timeouts_close(*tos);
*tos = NULL;
return 0;
}
static int
tos_new(lua_State *L)
{
timeout_t hz = luaL_optinteger(L, 1, 0);
struct timeouts **T;
int error;
T = lua_newuserdata(L, sizeof *T);
luaL_setmetatable(L, TIMEOUTS_METANAME);
lua_newtable(L);
lua_setuservalue(L, -2);
if (!(*T = timeouts_open(hz, &error)))
return luaL_error(L, "%s", strerror(error));
return 1;
}
static const luaL_Reg tos_methods[] = {
{ "update", &tos_update },
{ "step", &tos_step },
{ "timeout", &tos_timeout },
{ "add", &tos_add },
{ "del", &tos_del },
{ "get", &tos_get },
{ "pending", &tos_pending },
{ "expired", &tos_expired },
{ "check", &tos_check },
{ "timeouts", &tos_timeouts },
{ NULL, NULL },
};
static const luaL_Reg tos_metatable[] = {
{ "__gc", &tos__gc },
{ NULL, NULL },
};
static const luaL_Reg tos_globals[] = {
{ "new", &tos_new },
{ NULL, NULL },
};
static void
tos_newmetatable(lua_State *L)
{
if (luaL_newmetatable(L, TIMEOUTS_METANAME)) {
luaL_setfuncs(L, tos_metatable, 0);
luaL_newlib(L, tos_methods);
lua_setfield(L, -2, "__index");
}
}
int
luaopen_timeouts(lua_State *L)
{
to_newmetatable(L);
tos_newmetatable(L);
luaL_newlib(L, tos_globals);
lua_pushinteger(L, TIMEOUTS_PENDING);
lua_setfield(L, -2, "PENDING");
lua_pushinteger(L, TIMEOUTS_EXPIRED);
lua_setfield(L, -2, "EXPIRED");
lua_pushinteger(L, TIMEOUTS_ALL);
lua_setfield(L, -2, "ALL");
lua_pushinteger(L, TIMEOUTS_CLEAR);
lua_setfield(L, -2, "CLEAR");
return 1;
}

View File

@ -0,0 +1,530 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <limits.h>
#include "timeout.h"
#define THE_END_OF_TIME ((timeout_t)-1)
static int check_misc(void) {
if (TIMEOUT_VERSION != timeout_version())
return 1;
if (TIMEOUT_V_REL != timeout_v_rel())
return 1;
if (TIMEOUT_V_API != timeout_v_api())
return 1;
if (TIMEOUT_V_ABI != timeout_v_abi())
return 1;
if (strcmp(timeout_vendor(), TIMEOUT_VENDOR))
return 1;
return 0;
}
static int check_open_close(timeout_t hz_set, timeout_t hz_expect) {
int err=0;
struct timeouts *tos = timeouts_open(hz_set, &err);
if (!tos)
return 1;
if (err)
return 1;
if (hz_expect != timeouts_hz(tos))
return 1;
timeouts_close(tos);
return 0;
}
/* Not very random */
static timeout_t random_to(timeout_t min, timeout_t max)
{
if (max <= min)
return min;
/* Not actually all that random, but should exercise the code. */
timeout_t rand64 = random() * (timeout_t)INT_MAX + random();
return min + (rand64 % (max-min));
}
/* configuration for check_randomized */
struct rand_cfg {
/* When creating timeouts, smallest possible delay */
timeout_t min_timeout;
/* When creating timeouts, largest possible delay */
timeout_t max_timeout;
/* First time to start the clock at. */
timeout_t start_at;
/* Do not advance the clock past this time. */
timeout_t end_at;
/* Number of timeouts to create and monitor. */
int n_timeouts;
/* Advance the clock by no more than this each step. */
timeout_t max_step;
/* Use relative timers and stepping */
int relative;
/* Every time the clock ticks, try removing this many timeouts at
* random. */
int try_removing;
/* When we're done, advance the clock to the end of time. */
int finalize;
};
static int check_randomized(const struct rand_cfg *cfg)
{
#define FAIL() do { \
printf("Failure on line %d\n", __LINE__); \
goto done; \
} while (0)
int i, err;
int rv = 1;
struct timeout *t = calloc(cfg->n_timeouts, sizeof(struct timeout));
timeout_t *timeouts = calloc(cfg->n_timeouts, sizeof(timeout_t));
uint8_t *fired = calloc(cfg->n_timeouts, sizeof(uint8_t));
uint8_t *found = calloc(cfg->n_timeouts, sizeof(uint8_t));
uint8_t *deleted = calloc(cfg->n_timeouts, sizeof(uint8_t));
struct timeouts *tos = timeouts_open(0, &err);
timeout_t now = cfg->start_at;
int n_added_pending = 0, cnt_added_pending = 0;
int n_added_expired = 0, cnt_added_expired = 0;
struct timeouts_it it_p, it_e, it_all;
int p_done = 0, e_done = 0, all_done = 0;
struct timeout *to = NULL;
const int rel = cfg->relative;
if (!t || !timeouts || !tos || !fired || !found || !deleted)
FAIL();
timeouts_update(tos, cfg->start_at);
for (i = 0; i < cfg->n_timeouts; ++i) {
if (&t[i] != timeout_init(&t[i], rel ? 0 : TIMEOUT_ABS))
FAIL();
if (timeout_pending(&t[i]))
FAIL();
if (timeout_expired(&t[i]))
FAIL();
timeouts[i] = random_to(cfg->min_timeout, cfg->max_timeout);
timeouts_add(tos, &t[i], timeouts[i] - (rel ? now : 0));
if (timeouts[i] <= cfg->start_at) {
if (timeout_pending(&t[i]))
FAIL();
if (! timeout_expired(&t[i]))
FAIL();
++n_added_expired;
} else {
if (! timeout_pending(&t[i]))
FAIL();
if (timeout_expired(&t[i]))
FAIL();
++n_added_pending;
}
}
if (!!n_added_pending != timeouts_pending(tos))
FAIL();
if (!!n_added_expired != timeouts_expired(tos))
FAIL();
/* Test foreach, interleaving a few iterators. */
TIMEOUTS_IT_INIT(&it_p, TIMEOUTS_PENDING);
TIMEOUTS_IT_INIT(&it_e, TIMEOUTS_EXPIRED);
TIMEOUTS_IT_INIT(&it_all, TIMEOUTS_ALL);
while (! (p_done && e_done && all_done)) {
if (!p_done) {
to = timeouts_next(tos, &it_p);
if (to) {
i = to - &t[0];
++found[i];
++cnt_added_pending;
} else {
p_done = 1;
}
}
if (!e_done) {
to = timeouts_next(tos, &it_e);
if (to) {
i = to - &t[0];
++found[i];
++cnt_added_expired;
} else {
e_done = 1;
}
}
if (!all_done) {
to = timeouts_next(tos, &it_all);
if (to) {
i = to - &t[0];
++found[i];
} else {
all_done = 1;
}
}
}
for (i = 0; i < cfg->n_timeouts; ++i) {
if (found[i] != 2)
FAIL();
}
if (cnt_added_expired != n_added_expired)
FAIL();
if (cnt_added_pending != n_added_pending)
FAIL();
while (NULL != (to = timeouts_get(tos))) {
i = to - &t[0];
assert(&t[i] == to);
if (timeouts[i] > cfg->start_at)
FAIL(); /* shouldn't have happened yet */
--n_added_expired; /* drop expired timeouts. */
++fired[i];
}
if (n_added_expired != 0)
FAIL();
while (now < cfg->end_at) {
int n_fired_this_time = 0;
timeout_t first_at = timeouts_timeout(tos) + now;
timeout_t oldtime = now;
timeout_t step = random_to(1, cfg->max_step);
int another;
now += step;
if (rel)
timeouts_step(tos, step);
else
timeouts_update(tos, now);
for (i = 0; i < cfg->try_removing; ++i) {
int idx = random() % cfg->n_timeouts;
if (! fired[idx]) {
timeout_del(&t[idx]);
++deleted[idx];
}
}
another = (timeouts_timeout(tos) == 0);
while (NULL != (to = timeouts_get(tos))) {
if (! another)
FAIL(); /* Thought we saw the last one! */
i = to - &t[0];
assert(&t[i] == to);
if (timeouts[i] > now)
FAIL(); /* shouldn't have happened yet */
if (timeouts[i] <= oldtime)
FAIL(); /* should have happened already */
if (timeouts[i] < first_at)
FAIL(); /* first_at should've been earlier */
fired[i]++;
n_fired_this_time++;
another = (timeouts_timeout(tos) == 0);
}
if (n_fired_this_time && first_at > now)
FAIL(); /* first_at should've been earlier */
if (another)
FAIL(); /* Huh? We think there are more? */
if (!timeouts_check(tos, stderr))
FAIL();
}
for (i = 0; i < cfg->n_timeouts; ++i) {
if (fired[i] > 1)
FAIL(); /* Nothing fired twice. */
if (timeouts[i] <= now) {
if (!(fired[i] || deleted[i]))
FAIL();
} else {
if (fired[i])
FAIL();
}
if (fired[i] && deleted[i])
FAIL();
if (cfg->finalize > 1) {
if (!fired[i])
timeout_del(&t[i]);
}
}
/* Now nothing more should fire between now and the end of time. */
if (cfg->finalize) {
timeouts_update(tos, THE_END_OF_TIME);
if (cfg->finalize > 1) {
if (timeouts_get(tos))
FAIL();
TIMEOUTS_FOREACH(to, tos, TIMEOUTS_ALL)
FAIL();
}
}
rv = 0;
done:
if (tos) timeouts_close(tos);
if (t) free(t);
if (timeouts) free(timeouts);
if (fired) free(fired);
if (found) free(found);
if (deleted) free(deleted);
return rv;
}
struct intervals_cfg {
const timeout_t *timeouts;
int n_timeouts;
timeout_t start_at;
timeout_t end_at;
timeout_t skip;
};
int
check_intervals(struct intervals_cfg *cfg)
{
int i, err;
int rv = 1;
struct timeout *to;
struct timeout *t = calloc(cfg->n_timeouts, sizeof(struct timeout));
unsigned *fired = calloc(cfg->n_timeouts, sizeof(unsigned));
struct timeouts *tos = timeouts_open(0, &err);
timeout_t now = cfg->start_at;
if (!t || !tos || !fired)
FAIL();
timeouts_update(tos, now);
for (i = 0; i < cfg->n_timeouts; ++i) {
if (&t[i] != timeout_init(&t[i], TIMEOUT_INT))
FAIL();
if (timeout_pending(&t[i]))
FAIL();
if (timeout_expired(&t[i]))
FAIL();
timeouts_add(tos, &t[i], cfg->timeouts[i]);
if (! timeout_pending(&t[i]))
FAIL();
if (timeout_expired(&t[i]))
FAIL();
}
while (now < cfg->end_at) {
timeout_t delay = timeouts_timeout(tos);
if (cfg->skip && delay < cfg->skip)
delay = cfg->skip;
timeouts_step(tos, delay);
now += delay;
while (NULL != (to = timeouts_get(tos))) {
i = to - &t[0];
assert(&t[i] == to);
fired[i]++;
if (0 != (to->expires - cfg->start_at) % cfg->timeouts[i])
FAIL();
if (to->expires <= now)
FAIL();
if (to->expires > now + cfg->timeouts[i])
FAIL();
}
if (!timeouts_check(tos, stderr))
FAIL();
}
timeout_t duration = now - cfg->start_at;
for (i = 0; i < cfg->n_timeouts; ++i) {
if (cfg->skip) {
if (fired[i] > duration / cfg->timeouts[i])
FAIL();
} else {
if (fired[i] != duration / cfg->timeouts[i])
FAIL();
}
if (!timeout_pending(&t[i]))
FAIL();
}
rv = 0;
done:
if (t) free(t);
if (fired) free(fired);
if (tos) free(tos);
return rv;
}
int
main(int argc, char **argv)
{
int j;
int n_failed = 0;
#define DO(fn) do { \
printf("."); fflush(stdout); \
if (fn) { \
++n_failed; \
printf("%s failed\n", #fn); \
} \
} while (0)
#define DO_N(n, fn) do { \
for (j = 0; j < (n); ++j) { \
DO(fn); \
} \
} while (0)
DO(check_misc());
DO(check_open_close(1000, 1000));
DO(check_open_close(0, TIMEOUT_mHZ));
struct rand_cfg cfg1 = {
.min_timeout = 1,
.max_timeout = 100,
.start_at = 5,
.end_at = 1000,
.n_timeouts = 1000,
.max_step = 10,
.relative = 0,
.try_removing = 0,
.finalize = 2,
};
DO_N(300,check_randomized(&cfg1));
struct rand_cfg cfg2 = {
.min_timeout = 20,
.max_timeout = 1000,
.start_at = 10,
.end_at = 100,
.n_timeouts = 1000,
.max_step = 5,
.relative = 1,
.try_removing = 0,
.finalize = 2,
};
DO_N(300,check_randomized(&cfg2));
struct rand_cfg cfg2b = {
.min_timeout = 20,
.max_timeout = 1000,
.start_at = 10,
.end_at = 100,
.n_timeouts = 1000,
.max_step = 5,
.relative = 1,
.try_removing = 0,
.finalize = 1,
};
DO_N(300,check_randomized(&cfg2b));
struct rand_cfg cfg2c = {
.min_timeout = 20,
.max_timeout = 1000,
.start_at = 10,
.end_at = 100,
.n_timeouts = 1000,
.max_step = 5,
.relative = 1,
.try_removing = 0,
.finalize = 0,
};
DO_N(300,check_randomized(&cfg2c));
struct rand_cfg cfg3 = {
.min_timeout = 2000,
.max_timeout = ((uint64_t)1) << 50,
.start_at = 100,
.end_at = ((uint64_t)1) << 49,
.n_timeouts = 1000,
.max_step = 1<<31,
.relative = 0,
.try_removing = 0,
.finalize = 2,
};
DO_N(10,check_randomized(&cfg3));
struct rand_cfg cfg3b = {
.min_timeout = ((uint64_t)1) << 50,
.max_timeout = ((uint64_t)1) << 52,
.start_at = 100,
.end_at = ((uint64_t)1) << 53,
.n_timeouts = 1000,
.max_step = ((uint64_t)1)<<48,
.relative = 0,
.try_removing = 0,
.finalize = 2,
};
DO_N(10,check_randomized(&cfg3b));
struct rand_cfg cfg4 = {
.min_timeout = 2000,
.max_timeout = ((uint64_t)1) << 30,
.start_at = 100,
.end_at = ((uint64_t)1) << 26,
.n_timeouts = 10000,
.max_step = 1<<16,
.relative = 0,
.try_removing = 3,
.finalize = 2,
};
DO_N(10,check_randomized(&cfg4));
const timeout_t primes[] = {
2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,
59,61,67,71,73,79,83,89,97
};
const timeout_t factors_of_1337[] = {
1, 7, 191, 1337
};
const timeout_t multiples_of_five[] = {
5, 10, 15, 20, 25, 30, 35, 40, 45, 50
};
struct intervals_cfg icfg1 = {
.timeouts = primes,
.n_timeouts = sizeof(primes)/sizeof(timeout_t),
.start_at = 50,
.end_at = 5322,
.skip = 0,
};
DO(check_intervals(&icfg1));
struct intervals_cfg icfg2 = {
.timeouts = factors_of_1337,
.n_timeouts = sizeof(factors_of_1337)/sizeof(timeout_t),
.start_at = 50,
.end_at = 50000,
.skip = 0,
};
DO(check_intervals(&icfg2));
struct intervals_cfg icfg3 = {
.timeouts = multiples_of_five,
.n_timeouts = sizeof(multiples_of_five)/sizeof(timeout_t),
.start_at = 49,
.end_at = 5333,
.skip = 0,
};
DO(check_intervals(&icfg3));
struct intervals_cfg icfg4 = {
.timeouts = primes,
.n_timeouts = sizeof(primes)/sizeof(timeout_t),
.start_at = 50,
.end_at = 5322,
.skip = 16,
};
DO(check_intervals(&icfg4));
if (n_failed) {
puts("\nFAIL");
} else {
puts("\nOK");
}
return !!n_failed;
}
/* TODO:
* Solve PR#3.
* Investigate whether any untaken branches are possible.
*/

View File

@ -0,0 +1,249 @@
#include <stdint.h>
#ifdef _MSC_VER
#include <intrin.h> /* _BitScanForward, _BitScanReverse */
#endif
/* First define ctz and clz functions; these are compiler-dependent if
* you want them to be fast. */
#if defined(__GNUC__) && !defined(TIMEOUT_DISABLE_GNUC_BITOPS)
/* On GCC and clang and some others, we can use __builtin functions. They
* are not defined for n==0, but timeout.s never calls them with n==0. */
#define ctz64(n) __builtin_ctzll(n)
#define clz64(n) __builtin_clzll(n)
#if LONG_BITS == 32
#define ctz32(n) __builtin_ctzl(n)
#define clz32(n) __builtin_clzl(n)
#else
#define ctz32(n) __builtin_ctz(n)
#define clz32(n) __builtin_clz(n)
#endif
#elif defined(_MSC_VER) && !defined(TIMEOUT_DISABLE_MSVC_BITOPS)
/* On MSVC, we have these handy functions. We can ignore their return
* values, since we will never supply val == 0. */
static __inline int ctz32(unsigned long val)
{
DWORD zeros = 0;
_BitScanForward(&zeros, val);
return zeros;
}
static __inline int clz32(unsigned long val)
{
DWORD zeros = 0;
_BitScanReverse(&zeros, val);
return zeros;
}
#ifdef _WIN64
/* According to the documentation, these only exist on Win64. */
static __inline int ctz64(uint64_t val)
{
DWORD zeros = 0;
_BitScanForward64(&zeros, val);
return zeros;
}
static __inline int clz64(uint64_t val)
{
DWORD zeros = 0;
_BitScanReverse64(&zeros, val);
return zeros;
}
#else
static __inline int ctz64(uint64_t val)
{
uint32_t lo = (uint32_t) val;
uint32_t hi = (uint32_t) (val >> 32);
return lo ? ctz32(lo) : 32 + ctz32(hi);
}
static __inline int clz64(uint64_t val)
{
uint32_t lo = (uint32_t) val;
uint32_t hi = (uint32_t) (val >> 32);
return hi ? clz32(hi) : 32 + clz32(lo);
}
#endif
/* End of MSVC case. */
#else
/* TODO: There are more clever ways to do this in the generic case. */
#define process_(one, cz_bits, bits) \
if (x < ( one << (cz_bits - bits))) { rv += bits; x <<= bits; }
#define process64(bits) process_((UINT64_C(1)), 64, (bits))
static inline int clz64(uint64_t x)
{
int rv = 0;
process64(32);
process64(16);
process64(8);
process64(4);
process64(2);
process64(1);
return rv;
}
#define process32(bits) process_((UINT32_C(1)), 32, (bits))
static inline int clz32(uint32_t x)
{
int rv = 0;
process32(16);
process32(8);
process32(4);
process32(2);
process32(1);
return rv;
}
#undef process_
#undef process32
#undef process64
#define process_(one, bits) \
if ((x & ((one << (bits))-1)) == 0) { rv += bits; x >>= bits; }
#define process64(bits) process_((UINT64_C(1)), bits)
static inline int ctz64(uint64_t x)
{
int rv = 0;
process64(32);
process64(16);
process64(8);
process64(4);
process64(2);
process64(1);
return rv;
}
#define process32(bits) process_((UINT32_C(1)), bits)
static inline int ctz32(uint32_t x)
{
int rv = 0;
process32(16);
process32(8);
process32(4);
process32(2);
process32(1);
return rv;
}
#undef process32
#undef process64
#undef process_
/* End of generic case */
#endif /* End of defining ctz */
#ifdef TEST_BITOPS
#include <stdio.h>
#include <stdlib.h>
static uint64_t testcases[] = {
13371337 * 10,
100,
385789752,
82574,
(((uint64_t)1)<<63) + (((uint64_t)1)<<31) + 10101
};
static int
naive_clz(int bits, uint64_t v)
{
int r = 0;
uint64_t bit = ((uint64_t)1) << (bits-1);
while (bit && 0 == (v & bit)) {
r++;
bit >>= 1;
}
/* printf("clz(%d,%lx) -> %d\n", bits, v, r); */
return r;
}
static int
naive_ctz(int bits, uint64_t v)
{
int r = 0;
uint64_t bit = 1;
while (bit && 0 == (v & bit)) {
r++;
bit <<= 1;
if (r == bits)
break;
}
/* printf("ctz(%d,%lx) -> %d\n", bits, v, r); */
return r;
}
static int
check(uint64_t vv)
{
uint32_t v32 = (uint32_t) vv;
if (vv == 0)
return 1; /* c[tl]z64(0) is undefined. */
if (ctz64(vv) != naive_ctz(64, vv)) {
printf("mismatch with ctz64: %d\n", ctz64(vv));
exit(1);
return 0;
}
if (clz64(vv) != naive_clz(64, vv)) {
printf("mismatch with clz64: %d\n", clz64(vv));
exit(1);
return 0;
}
if (v32 == 0)
return 1; /* c[lt]z(0) is undefined. */
if (ctz32(v32) != naive_ctz(32, v32)) {
printf("mismatch with ctz32: %d\n", ctz32(v32));
exit(1);
return 0;
}
if (clz32(v32) != naive_clz(32, v32)) {
printf("mismatch with clz32: %d\n", clz32(v32));
exit(1);
return 0;
}
return 1;
}
int
main(int c, char **v)
{
unsigned int i;
const unsigned int n = sizeof(testcases)/sizeof(testcases[0]);
int result = 0;
for (i = 0; i <= 63; ++i) {
uint64_t x = 1 << i;
if (!check(x))
result = 1;
--x;
if (!check(x))
result = 1;
}
for (i = 0; i < n; ++i) {
if (! check(testcases[i]))
result = 1;
}
if (result) {
puts("FAIL");
} else {
puts("OK");
}
return result;
}
#endif

View File

@ -0,0 +1,77 @@
/*
* D E B U G R O U T I N E S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#if TIMEOUT_DEBUG - 0
#include <stdlib.h>
#include <stdio.h>
#undef TIMEOUT_DEBUG
#define TIMEOUT_DEBUG 1
#define DEBUG_LEVEL timeout_debug
static int timeout_debug;
#define SAYit_(lvl, fmt, ...) do { \
if (DEBUG_LEVEL >= (lvl)) \
fprintf(stderr, fmt "%s", __FILE__, __LINE__, __func__, __VA_ARGS__); \
} while (0)
#define SAYit(lvl, ...) SAYit_((lvl), "%s:%d:%s: " __VA_ARGS__, "\n")
#define PANIC(...) do { \
SAYit(0, __VA_ARGS__); \
_Exit(EXIT_FAILURE); \
} while (0)
#else
#undef TIMEOUT_DEBUG
#define TIMEOUT_DEBUG 0
#define DEBUG_LEVEL 0
#define SAYit(...) (void)0
#endif
#define SAY(...) SAYit(1, __VA_ARGS__)
#define HAI SAY("HAI")
static inline char *fmt_(char *buf, uint64_t ts, int wheel_bit, int wheel_num) {
char *p = buf;
int wheel, n, i;
for (wheel = wheel_num - 2; wheel >= 0; wheel--) {
n = ((1 << wheel_bit) - 1) & (ts >> (wheel * WHEEL_BIT));
for (i = wheel_bit - 1; i >= 0; i--) {
*p++ = '0' + !!(n & (1 << i));
}
if (wheel != 0)
*p++ = ':';
}
*p = 0;
return buf;
} /* fmt_() */
#define fmt(ts) fmt_(((char[((1 << WHEEL_BIT) * WHEEL_NUM) + WHEEL_NUM + 1]){ 0 }), (ts), WHEEL_BIT, WHEEL_NUM)
static inline char *bin64_(char *buf, uint64_t n, int wheel_bit) {
char *p = buf;
int i;
for (i = 0; i < (1 << wheel_bit); i++) {
*p++ = "01"[0x1 & (n >> (((1 << wheel_bit) - 1) - i))];
}
*p = 0;
return buf;
} /* bin64_() */
#define bin64(ts) bin64_(((char[((1 << WHEEL_BIT) * WHEEL_NUM) + 1]){ 0 }), (ts), WHEEL_BIT)

744
src/ext/timeouts/timeout.c Normal file
View File

@ -0,0 +1,744 @@
/* ==========================================================================
* timeout.c - Tickless hierarchical timing wheel.
* --------------------------------------------------------------------------
* Copyright (c) 2013, 2014 William Ahern
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to permit
* persons to whom the Software is furnished to do so, subject to the
* following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
* NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
* USE OR OTHER DEALINGS IN THE SOFTWARE.
* ==========================================================================
*/
#include <limits.h> /* CHAR_BIT */
#include <stddef.h> /* NULL */
#include <stdlib.h> /* malloc(3) free(3) */
#include <stdio.h> /* FILE fprintf(3) */
#include <inttypes.h> /* UINT64_C uint64_t */
#include <string.h> /* memset(3) */
#include <errno.h> /* errno */
#include <sys/queue.h> /* TAILQ(3) */
#include "timeout.h"
#if TIMEOUT_DEBUG - 0
#include "timeout-debug.h"
#endif
#ifdef TIMEOUT_DISABLE_RELATIVE_ACCESS
#define TO_SET_TIMEOUTS(to, T) ((void)0)
#else
#define TO_SET_TIMEOUTS(to, T) ((to)->timeouts = (T))
#endif
/*
* A N C I L L A R Y R O U T I N E S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#define abstime_t timeout_t /* for documentation purposes */
#define reltime_t timeout_t /* "" */
#if !defined countof
#define countof(a) (sizeof (a) / sizeof *(a))
#endif
#if !defined endof
#define endof(a) (&(a)[countof(a)])
#endif
#if !defined MIN
#define MIN(a, b) (((a) < (b))? (a) : (b))
#endif
#if !defined MAX
#define MAX(a, b) (((a) > (b))? (a) : (b))
#endif
#if !defined TAILQ_CONCAT
#define TAILQ_CONCAT(head1, head2, field) do { \
if (!TAILQ_EMPTY(head2)) { \
*(head1)->tqh_last = (head2)->tqh_first; \
(head2)->tqh_first->field.tqe_prev = (head1)->tqh_last; \
(head1)->tqh_last = (head2)->tqh_last; \
TAILQ_INIT((head2)); \
} \
} while (0)
#endif
#if !defined TAILQ_FOREACH_SAFE
#define TAILQ_FOREACH_SAFE(var, head, field, tvar) \
for ((var) = TAILQ_FIRST(head); \
(var) && ((tvar) = TAILQ_NEXT(var, field), 1); \
(var) = (tvar))
#endif
/*
* B I T M A N I P U L A T I O N R O U T I N E S
*
* The macros and routines below implement wheel parameterization. The
* inputs are:
*
* WHEEL_BIT - The number of value bits mapped in each wheel. The
* lowest-order WHEEL_BIT bits index the lowest-order (highest
* resolution) wheel, the next group of WHEEL_BIT bits the
* higher wheel, etc.
*
* WHEEL_NUM - The number of wheels. WHEEL_BIT * WHEEL_NUM = the number of
* value bits used by all the wheels. For the default of 6 and
* 4, only the low 24 bits are processed. Any timeout value
* larger than this will cycle through again.
*
* The implementation uses bit fields to remember which slot in each wheel
* is populated, and to generate masks of expiring slots according to the
* current update interval (i.e. the "tickless" aspect). The slots to
* process in a wheel are (populated-set & interval-mask).
*
* WHEEL_BIT cannot be larger than 6 bits because 2^6 -> 64 is the largest
* number of slots which can be tracked in a uint64_t integer bit field.
* WHEEL_BIT cannot be smaller than 3 bits because of our rotr and rotl
* routines, which only operate on all the value bits in an integer, and
* there's no integer smaller than uint8_t.
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#if !defined WHEEL_BIT
#define WHEEL_BIT 6
#endif
#if !defined WHEEL_NUM
#define WHEEL_NUM 4
#endif
#define WHEEL_LEN (1U << WHEEL_BIT)
#define WHEEL_MAX (WHEEL_LEN - 1)
#define WHEEL_MASK (WHEEL_LEN - 1)
#define TIMEOUT_MAX ((TIMEOUT_C(1) << (WHEEL_BIT * WHEEL_NUM)) - 1)
#include "timeout-bitops.c"
#if WHEEL_BIT == 6
#define ctz(n) ctz64(n)
#define clz(n) clz64(n)
#define fls(n) ((int)(64 - clz64(n)))
#else
#define ctz(n) ctz32(n)
#define clz(n) clz32(n)
#define fls(n) ((int)(32 - clz32(n)))
#endif
#if WHEEL_BIT == 6
#define WHEEL_C(n) UINT64_C(n)
#define WHEEL_PRIu PRIu64
#define WHEEL_PRIx PRIx64
typedef uint64_t wheel_t;
#elif WHEEL_BIT == 5
#define WHEEL_C(n) UINT32_C(n)
#define WHEEL_PRIu PRIu32
#define WHEEL_PRIx PRIx32
typedef uint32_t wheel_t;
#elif WHEEL_BIT == 4
#define WHEEL_C(n) UINT16_C(n)
#define WHEEL_PRIu PRIu16
#define WHEEL_PRIx PRIx16
typedef uint16_t wheel_t;
#elif WHEEL_BIT == 3
#define WHEEL_C(n) UINT8_C(n)
#define WHEEL_PRIu PRIu8
#define WHEEL_PRIx PRIx8
typedef uint8_t wheel_t;
#else
#error invalid WHEEL_BIT value
#endif
static inline wheel_t rotl(const wheel_t v, int c) {
if (!(c &= (sizeof v * CHAR_BIT - 1)))
return v;
return (v << c) | (v >> (sizeof v * CHAR_BIT - c));
} /* rotl() */
static inline wheel_t rotr(const wheel_t v, int c) {
if (!(c &= (sizeof v * CHAR_BIT - 1)))
return v;
return (v >> c) | (v << (sizeof v * CHAR_BIT - c));
} /* rotr() */
/*
* T I M E R R O U T I N E S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
TAILQ_HEAD(timeout_list, timeout);
struct timeouts {
struct timeout_list wheel[WHEEL_NUM][WHEEL_LEN], expired;
wheel_t pending[WHEEL_NUM];
timeout_t curtime;
timeout_t hertz;
}; /* struct timeouts */
static struct timeouts *timeouts_init(struct timeouts *T, timeout_t hz) {
unsigned i, j;
for (i = 0; i < countof(T->wheel); i++) {
for (j = 0; j < countof(T->wheel[i]); j++) {
TAILQ_INIT(&T->wheel[i][j]);
}
}
TAILQ_INIT(&T->expired);
for (i = 0; i < countof(T->pending); i++) {
T->pending[i] = 0;
}
T->curtime = 0;
T->hertz = (hz)? hz : TIMEOUT_mHZ;
return T;
} /* timeouts_init() */
TIMEOUT_PUBLIC struct timeouts *timeouts_open(timeout_t hz, int *error) {
struct timeouts *T;
if ((T = malloc(sizeof *T)))
return timeouts_init(T, hz);
*error = errno;
return NULL;
} /* timeouts_open() */
static void timeouts_reset(struct timeouts *T) {
struct timeout_list reset;
struct timeout *to;
unsigned i, j;
TAILQ_INIT(&reset);
for (i = 0; i < countof(T->wheel); i++) {
for (j = 0; j < countof(T->wheel[i]); j++) {
TAILQ_CONCAT(&reset, &T->wheel[i][j], tqe);
}
}
TAILQ_CONCAT(&reset, &T->expired, tqe);
TAILQ_FOREACH(to, &reset, tqe) {
to->pending = NULL;
TO_SET_TIMEOUTS(to, NULL);
}
} /* timeouts_reset() */
TIMEOUT_PUBLIC void timeouts_close(struct timeouts *T) {
/*
* NOTE: Delete installed timeouts so timeout_pending() and
* timeout_expired() worked as expected.
*/
timeouts_reset(T);
free(T);
} /* timeouts_close() */
TIMEOUT_PUBLIC timeout_t timeouts_hz(struct timeouts *T) {
return T->hertz;
} /* timeouts_hz() */
TIMEOUT_PUBLIC void timeouts_del(struct timeouts *T, struct timeout *to) {
if (to->pending) {
TAILQ_REMOVE(to->pending, to, tqe);
if (to->pending != &T->expired && TAILQ_EMPTY(to->pending)) {
ptrdiff_t index = to->pending - &T->wheel[0][0];
int wheel = index / WHEEL_LEN;
int slot = index % WHEEL_LEN;
T->pending[wheel] &= ~(WHEEL_C(1) << slot);
}
to->pending = NULL;
TO_SET_TIMEOUTS(to, NULL);
}
} /* timeouts_del() */
static inline reltime_t timeout_rem(struct timeouts *T, struct timeout *to) {
return to->expires - T->curtime;
} /* timeout_rem() */
static inline int timeout_wheel(timeout_t timeout) {
/* must be called with timeout != 0, so fls input is nonzero */
return (fls(MIN(timeout, TIMEOUT_MAX)) - 1) / WHEEL_BIT;
} /* timeout_wheel() */
static inline int timeout_slot(int wheel, timeout_t expires) {
return WHEEL_MASK & ((expires >> (wheel * WHEEL_BIT)) - !!wheel);
} /* timeout_slot() */
static void timeouts_sched(struct timeouts *T, struct timeout *to, timeout_t expires) {
timeout_t rem;
int wheel, slot;
timeouts_del(T, to);
to->expires = expires;
TO_SET_TIMEOUTS(to, T);
if (expires > T->curtime) {
rem = timeout_rem(T, to);
/* rem is nonzero since:
* rem == timeout_rem(T,to),
* == to->expires - T->curtime
* and above we have expires > T->curtime.
*/
wheel = timeout_wheel(rem);
slot = timeout_slot(wheel, to->expires);
to->pending = &T->wheel[wheel][slot];
TAILQ_INSERT_TAIL(to->pending, to, tqe);
T->pending[wheel] |= WHEEL_C(1) << slot;
} else {
to->pending = &T->expired;
TAILQ_INSERT_TAIL(to->pending, to, tqe);
}
} /* timeouts_sched() */
#ifndef TIMEOUT_DISABLE_INTERVALS
static void timeouts_readd(struct timeouts *T, struct timeout *to) {
to->expires += to->interval;
if (to->expires <= T->curtime) {
/* If we've missed the next firing of this timeout, reschedule
* it to occur at the next multiple of its interval after
* the last time that it fired.
*/
timeout_t n = T->curtime - to->expires;
timeout_t r = n % to->interval;
to->expires = T->curtime + (to->interval - r);
}
timeouts_sched(T, to, to->expires);
} /* timeouts_readd() */
#endif
TIMEOUT_PUBLIC void timeouts_add(struct timeouts *T, struct timeout *to, timeout_t timeout) {
#ifndef TIMEOUT_DISABLE_INTERVALS
if (to->flags & TIMEOUT_INT)
to->interval = MAX(1, timeout);
#endif
if (to->flags & TIMEOUT_ABS)
timeouts_sched(T, to, timeout);
else
timeouts_sched(T, to, T->curtime + timeout);
} /* timeouts_add() */
TIMEOUT_PUBLIC void timeouts_update(struct timeouts *T, abstime_t curtime) {
timeout_t elapsed = curtime - T->curtime;
struct timeout_list todo;
int wheel;
TAILQ_INIT(&todo);
/*
* There's no avoiding looping over every wheel. It's best to keep
* WHEEL_NUM smallish.
*/
for (wheel = 0; wheel < WHEEL_NUM; wheel++) {
wheel_t pending;
/*
* Calculate the slots expiring in this wheel
*
* If the elapsed time is greater than the maximum period of
* the wheel, mark every position as expiring.
*
* Otherwise, to determine the expired slots fill in all the
* bits between the last slot processed and the current
* slot, inclusive of the last slot. We'll bitwise-AND this
* with our pending set below.
*
* If a wheel rolls over, force a tick of the next higher
* wheel.
*/
if ((elapsed >> (wheel * WHEEL_BIT)) > WHEEL_MAX) {
pending = (wheel_t)~WHEEL_C(0);
} else {
wheel_t _elapsed = WHEEL_MASK & (elapsed >> (wheel * WHEEL_BIT));
int oslot, nslot;
/*
* TODO: It's likely that at least one of the
* following three bit fill operations is redundant
* or can be replaced with a simpler operation.
*/
oslot = WHEEL_MASK & (T->curtime >> (wheel * WHEEL_BIT));
pending = rotl(((UINT64_C(1) << _elapsed) - 1), oslot);
nslot = WHEEL_MASK & (curtime >> (wheel * WHEEL_BIT));
pending |= rotr(rotl(((WHEEL_C(1) << _elapsed) - 1), nslot), _elapsed);
pending |= WHEEL_C(1) << nslot;
}
while (pending & T->pending[wheel]) {
/* ctz input cannot be zero: loop condition. */
int slot = ctz(pending & T->pending[wheel]);
TAILQ_CONCAT(&todo, &T->wheel[wheel][slot], tqe);
T->pending[wheel] &= ~(UINT64_C(1) << slot);
}
if (!(0x1 & pending))
break; /* break if we didn't wrap around end of wheel */
/* if we're continuing, the next wheel must tick at least once */
elapsed = MAX(elapsed, (WHEEL_LEN << (wheel * WHEEL_BIT)));
}
T->curtime = curtime;
while (!TAILQ_EMPTY(&todo)) {
struct timeout *to = TAILQ_FIRST(&todo);
TAILQ_REMOVE(&todo, to, tqe);
to->pending = NULL;
timeouts_sched(T, to, to->expires);
}
return;
} /* timeouts_update() */
TIMEOUT_PUBLIC void timeouts_step(struct timeouts *T, reltime_t elapsed) {
timeouts_update(T, T->curtime + elapsed);
} /* timeouts_step() */
TIMEOUT_PUBLIC bool timeouts_pending(struct timeouts *T) {
wheel_t pending = 0;
int wheel;
for (wheel = 0; wheel < WHEEL_NUM; wheel++) {
pending |= T->pending[wheel];
}
return !!pending;
} /* timeouts_pending() */
TIMEOUT_PUBLIC bool timeouts_expired(struct timeouts *T) {
return !TAILQ_EMPTY(&T->expired);
} /* timeouts_expired() */
/*
* Calculate the interval before needing to process any timeouts pending on
* any wheel.
*
* (This is separated from the public API routine so we can evaluate our
* wheel invariant assertions irrespective of the expired queue.)
*
* This might return a timeout value sooner than any installed timeout if
* only higher-order wheels have timeouts pending. We can only know when to
* process a wheel, not precisely when a timeout is scheduled. Our timeout
* accuracy could be off by 2^(N*M)-1 units where N is the wheel number and
* M is WHEEL_BIT. Only timeouts which have fallen through to wheel 0 can be
* known exactly.
*
* We should never return a timeout larger than the lowest actual timeout.
*/
static timeout_t timeouts_int(struct timeouts *T) {
timeout_t timeout = ~TIMEOUT_C(0), _timeout;
timeout_t relmask;
int wheel, slot;
relmask = 0;
for (wheel = 0; wheel < WHEEL_NUM; wheel++) {
if (T->pending[wheel]) {
slot = WHEEL_MASK & (T->curtime >> (wheel * WHEEL_BIT));
/* ctz input cannot be zero: T->pending[wheel] is
* nonzero, so rotr() is nonzero. */
_timeout = (ctz(rotr(T->pending[wheel], slot)) + !!wheel) << (wheel * WHEEL_BIT);
/* +1 to higher order wheels as those timeouts are one rotation in the future (otherwise they'd be on a lower wheel or expired) */
_timeout -= relmask & T->curtime;
/* reduce by how much lower wheels have progressed */
timeout = MIN(_timeout, timeout);
}
relmask <<= WHEEL_BIT;
relmask |= WHEEL_MASK;
}
return timeout;
} /* timeouts_int() */
/*
* Calculate the interval our caller can wait before needing to process
* events.
*/
TIMEOUT_PUBLIC timeout_t timeouts_timeout(struct timeouts *T) {
if (!TAILQ_EMPTY(&T->expired))
return 0;
return timeouts_int(T);
} /* timeouts_timeout() */
TIMEOUT_PUBLIC struct timeout *timeouts_get(struct timeouts *T) {
if (!TAILQ_EMPTY(&T->expired)) {
struct timeout *to = TAILQ_FIRST(&T->expired);
TAILQ_REMOVE(&T->expired, to, tqe);
to->pending = NULL;
TO_SET_TIMEOUTS(to, NULL);
#ifndef TIMEOUT_DISABLE_INTERVALS
if ((to->flags & TIMEOUT_INT) && to->interval > 0)
timeouts_readd(T, to);
#endif
return to;
} else {
return 0;
}
} /* timeouts_get() */
/*
* Use dumb looping to locate the earliest timeout pending on the wheel so
* our invariant assertions can check the result of our optimized code.
*/
static struct timeout *timeouts_min(struct timeouts *T) {
struct timeout *to, *min = NULL;
unsigned i, j;
for (i = 0; i < countof(T->wheel); i++) {
for (j = 0; j < countof(T->wheel[i]); j++) {
TAILQ_FOREACH(to, &T->wheel[i][j], tqe) {
if (!min || to->expires < min->expires)
min = to;
}
}
}
return min;
} /* timeouts_min() */
/*
* Check some basic algorithm invariants. If these invariants fail then
* something is definitely broken.
*/
#define report(...) do { \
if ((fp)) \
fprintf(fp, __VA_ARGS__); \
} while (0)
#define check(expr, ...) do { \
if (!(expr)) { \
report(__VA_ARGS__); \
return 0; \
} \
} while (0)
TIMEOUT_PUBLIC bool timeouts_check(struct timeouts *T, FILE *fp) {
timeout_t timeout;
struct timeout *to;
if ((to = timeouts_min(T))) {
check(to->expires > T->curtime, "missed timeout (expires:%" TIMEOUT_PRIu " <= curtime:%" TIMEOUT_PRIu ")\n", to->expires, T->curtime);
timeout = timeouts_int(T);
check(timeout <= to->expires - T->curtime, "wrong soft timeout (soft:%" TIMEOUT_PRIu " > hard:%" TIMEOUT_PRIu ") (expires:%" TIMEOUT_PRIu "; curtime:%" TIMEOUT_PRIu ")\n", timeout, (to->expires - T->curtime), to->expires, T->curtime);
timeout = timeouts_timeout(T);
check(timeout <= to->expires - T->curtime, "wrong soft timeout (soft:%" TIMEOUT_PRIu " > hard:%" TIMEOUT_PRIu ") (expires:%" TIMEOUT_PRIu "; curtime:%" TIMEOUT_PRIu ")\n", timeout, (to->expires - T->curtime), to->expires, T->curtime);
} else {
timeout = timeouts_timeout(T);
if (!TAILQ_EMPTY(&T->expired))
check(timeout == 0, "wrong soft timeout (soft:%" TIMEOUT_PRIu " != hard:%" TIMEOUT_PRIu ")\n", timeout, TIMEOUT_C(0));
else
check(timeout == ~TIMEOUT_C(0), "wrong soft timeout (soft:%" TIMEOUT_PRIu " != hard:%" TIMEOUT_PRIu ")\n", timeout, ~TIMEOUT_C(0));
}
return 1;
} /* timeouts_check() */
#define ENTER \
do { \
static const int pc0 = __LINE__; \
switch (pc0 + it->pc) { \
case __LINE__: (void)0
#define SAVE_AND_DO(do_statement) \
do { \
it->pc = __LINE__ - pc0; \
do_statement; \
case __LINE__: (void)0; \
} while (0)
#define YIELD(rv) \
SAVE_AND_DO(return (rv))
#define LEAVE \
SAVE_AND_DO(break); \
} \
} while (0)
TIMEOUT_PUBLIC struct timeout *timeouts_next(struct timeouts *T, struct timeouts_it *it) {
struct timeout *to;
ENTER;
if (it->flags & TIMEOUTS_EXPIRED) {
if (it->flags & TIMEOUTS_CLEAR) {
while ((to = timeouts_get(T))) {
YIELD(to);
}
} else {
TAILQ_FOREACH_SAFE(to, &T->expired, tqe, it->to) {
YIELD(to);
}
}
}
if (it->flags & TIMEOUTS_PENDING) {
for (it->i = 0; it->i < countof(T->wheel); it->i++) {
for (it->j = 0; it->j < countof(T->wheel[it->i]); it->j++) {
TAILQ_FOREACH_SAFE(to, &T->wheel[it->i][it->j], tqe, it->to) {
YIELD(to);
}
}
}
}
LEAVE;
return NULL;
} /* timeouts_next */
#undef LEAVE
#undef YIELD
#undef SAVE_AND_DO
#undef ENTER
/*
* T I M E O U T R O U T I N E S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
TIMEOUT_PUBLIC struct timeout *timeout_init(struct timeout *to, int flags) {
memset(to, 0, sizeof *to);
to->flags = flags;
return to;
} /* timeout_init() */
#ifndef TIMEOUT_DISABLE_RELATIVE_ACCESS
TIMEOUT_PUBLIC bool timeout_pending(struct timeout *to) {
return to->pending && to->pending != &to->timeouts->expired;
} /* timeout_pending() */
TIMEOUT_PUBLIC bool timeout_expired(struct timeout *to) {
return to->pending && to->pending == &to->timeouts->expired;
} /* timeout_expired() */
TIMEOUT_PUBLIC void timeout_del(struct timeout *to) {
timeouts_del(to->timeouts, to);
} /* timeout_del() */
#endif
/*
* V E R S I O N I N T E R F A C E S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
TIMEOUT_PUBLIC int timeout_version(void) {
return TIMEOUT_VERSION;
} /* timeout_version() */
TIMEOUT_PUBLIC const char *timeout_vendor(void) {
return TIMEOUT_VENDOR;
} /* timeout_version() */
TIMEOUT_PUBLIC int timeout_v_rel(void) {
return TIMEOUT_V_REL;
} /* timeout_version() */
TIMEOUT_PUBLIC int timeout_v_abi(void) {
return TIMEOUT_V_ABI;
} /* timeout_version() */
TIMEOUT_PUBLIC int timeout_v_api(void) {
return TIMEOUT_V_API;
} /* timeout_version() */

253
src/ext/timeouts/timeout.h Normal file
View File

@ -0,0 +1,253 @@
/* ==========================================================================
* timeout.h - Tickless hierarchical timing wheel.
* --------------------------------------------------------------------------
* Copyright (c) 2013, 2014 William Ahern
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to permit
* persons to whom the Software is furnished to do so, subject to the
* following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
* NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
* USE OR OTHER DEALINGS IN THE SOFTWARE.
* ==========================================================================
*/
#ifndef TIMEOUT_H
#define TIMEOUT_H
#include <stdbool.h> /* bool */
#include <stdio.h> /* FILE */
#include <inttypes.h> /* PRIu64 PRIx64 PRIX64 uint64_t */
#include <sys/queue.h> /* TAILQ(3) */
/*
* V E R S I O N I N T E R F A C E S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#if !defined TIMEOUT_PUBLIC
#define TIMEOUT_PUBLIC
#endif
#define TIMEOUT_VERSION TIMEOUT_V_REL
#define TIMEOUT_VENDOR "william@25thandClement.com"
#define TIMEOUT_V_REL 0x20160226
#define TIMEOUT_V_ABI 0x20160224
#define TIMEOUT_V_API 0x20160226
TIMEOUT_PUBLIC int timeout_version(void);
TIMEOUT_PUBLIC const char *timeout_vendor(void);
TIMEOUT_PUBLIC int timeout_v_rel(void);
TIMEOUT_PUBLIC int timeout_v_abi(void);
TIMEOUT_PUBLIC int timeout_v_api(void);
/*
* I N T E G E R T Y P E I N T E R F A C E S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#define TIMEOUT_C(n) UINT64_C(n)
#define TIMEOUT_PRIu PRIu64
#define TIMEOUT_PRIx PRIx64
#define TIMEOUT_PRIX PRIX64
#define TIMEOUT_mHZ TIMEOUT_C(1000)
#define TIMEOUT_uHZ TIMEOUT_C(1000000)
#define TIMEOUT_nHZ TIMEOUT_C(1000000000)
typedef uint64_t timeout_t;
#define timeout_error_t int /* for documentation purposes */
/*
* C A L L B A C K I N T E R F A C E
*
* Callback function parameters unspecified to make embedding into existing
* applications easier.
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef TIMEOUT_CB_OVERRIDE
struct timeout_cb {
void (*fn)();
void *arg;
}; /* struct timeout_cb */
#endif
/*
* T I M E O U T I N T E R F A C E S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef TIMEOUT_DISABLE_INTERVALS
#define TIMEOUT_INT 0x01 /* interval (repeating) timeout */
#endif
#define TIMEOUT_ABS 0x02 /* treat timeout values as absolute */
#define TIMEOUT_INITIALIZER(flags) { (flags) }
#define timeout_setcb(to, fn, arg) do { \
(to)->callback.fn = (fn); \
(to)->callback.arg = (arg); \
} while (0)
struct timeout {
int flags;
timeout_t expires;
/* absolute expiration time */
struct timeout_list *pending;
/* timeout list if pending on wheel or expiry queue */
TAILQ_ENTRY(timeout) tqe;
/* entry member for struct timeout_list lists */
#ifndef TIMEOUT_DISABLE_CALLBACKS
struct timeout_cb callback;
/* optional callback information */
#endif
#ifndef TIMEOUT_DISABLE_INTERVALS
timeout_t interval;
/* timeout interval if periodic */
#endif
#ifndef TIMEOUT_DISABLE_RELATIVE_ACCESS
struct timeouts *timeouts;
/* timeouts collection if member of */
#endif
}; /* struct timeout */
TIMEOUT_PUBLIC struct timeout *timeout_init(struct timeout *, int);
/* initialize timeout structure (same as TIMEOUT_INITIALIZER) */
#ifndef TIMEOUT_DISABLE_RELATIVE_ACCESS
TIMEOUT_PUBLIC bool timeout_pending(struct timeout *);
/* true if on timing wheel, false otherwise */
TIMEOUT_PUBLIC bool timeout_expired(struct timeout *);
/* true if on expired queue, false otherwise */
TIMEOUT_PUBLIC void timeout_del(struct timeout *);
/* remove timeout from any timing wheel (okay if not member of any) */
#endif
/*
* T I M I N G W H E E L I N T E R F A C E S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
struct timeouts;
TIMEOUT_PUBLIC struct timeouts *timeouts_open(timeout_t, timeout_error_t *);
/* open a new timing wheel, setting optional HZ (for float conversions) */
TIMEOUT_PUBLIC void timeouts_close(struct timeouts *);
/* destroy timing wheel */
TIMEOUT_PUBLIC timeout_t timeouts_hz(struct timeouts *);
/* return HZ setting (for float conversions) */
TIMEOUT_PUBLIC void timeouts_update(struct timeouts *, timeout_t);
/* update timing wheel with current absolute time */
TIMEOUT_PUBLIC void timeouts_step(struct timeouts *, timeout_t);
/* step timing wheel by relative time */
TIMEOUT_PUBLIC timeout_t timeouts_timeout(struct timeouts *);
/* return interval to next required update */
TIMEOUT_PUBLIC void timeouts_add(struct timeouts *, struct timeout *, timeout_t);
/* add timeout to timing wheel */
TIMEOUT_PUBLIC void timeouts_del(struct timeouts *, struct timeout *);
/* remove timeout from any timing wheel or expired queue (okay if on neither) */
TIMEOUT_PUBLIC struct timeout *timeouts_get(struct timeouts *);
/* return any expired timeout (caller should loop until NULL-return) */
TIMEOUT_PUBLIC bool timeouts_pending(struct timeouts *);
/* return true if any timeouts pending on timing wheel */
TIMEOUT_PUBLIC bool timeouts_expired(struct timeouts *);
/* return true if any timeouts on expired queue */
TIMEOUT_PUBLIC bool timeouts_check(struct timeouts *, FILE *);
/* return true if invariants hold. describes failures to optional file handle. */
#define TIMEOUTS_PENDING 0x10
#define TIMEOUTS_EXPIRED 0x20
#define TIMEOUTS_ALL (TIMEOUTS_PENDING|TIMEOUTS_EXPIRED)
#define TIMEOUTS_CLEAR 0x40
#define TIMEOUTS_IT_INITIALIZER(flags) { (flags), 0, 0, 0, 0 }
#define TIMEOUTS_IT_INIT(cur, _flags) do { \
(cur)->flags = (_flags); \
(cur)->pc = 0; \
} while (0)
struct timeouts_it {
int flags;
unsigned pc, i, j;
struct timeout *to;
}; /* struct timeouts_it */
TIMEOUT_PUBLIC struct timeout *timeouts_next(struct timeouts *, struct timeouts_it *);
/* return next timeout in pending wheel or expired queue. caller can delete
* the returned timeout, but should not otherwise manipulate the timing
* wheel. in particular, caller SHOULD NOT delete any other timeout as that
* could invalidate cursor state and trigger a use-after-free.
*/
#define TIMEOUTS_FOREACH(var, T, flags) \
struct timeouts_it _it = TIMEOUTS_IT_INITIALIZER((flags)); \
while (((var) = timeouts_next((T), &_it)))
/*
* B O N U S W H E E L I N T E R F A C E S
*
* I usually use floating point timeouts in all my code, but it's cleaner to
* separate it to keep the core algorithmic code simple.
*
* Using macros instead of static inline routines where <math.h> routines
* might be used to keep -lm linking optional.
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include <math.h> /* ceil(3) */
#define timeouts_f2i(T, f) \
((timeout_t)ceil((f) * timeouts_hz((T)))) /* prefer late expiration over early */
#define timeouts_i2f(T, i) \
((double)(i) / timeouts_hz((T)))
#define timeouts_addf(T, to, timeout) \
timeouts_add((T), (to), timeouts_f2i((T), (timeout)))
#endif /* TIMEOUT_H */