├── .gitignore ├── Makefile ├── Rules.shrc ├── bench ├── Rules.mk ├── bench-add.lua ├── bench-aux.lua ├── bench-del.lua ├── bench-expire.lua ├── bench-heap.c ├── bench-llrb.c ├── bench-wheel.c ├── bench.c ├── bench.h └── bench.plt ├── lua ├── Rules.mk └── timeout-lua.c ├── test-timeout.c ├── timeout-bitops.c ├── timeout-debug.h ├── timeout.c └── timeout.h /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.gcda 3 | *.gcov 4 | *.gcno 5 | *~ 6 | *.so 7 | *.dSYM 8 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # NOTE: GNU Make 3.81 won't export MAKEFLAGS if .POSIX is specified, but 2 | # Solaris make won't export MAKEFLAGS unless .POSIX is specified. 3 | $(firstword ignore).POSIX: 4 | 5 | .DEFAULT_GOAL = all 6 | 7 | .SUFFIXES: 8 | 9 | all: 10 | 11 | # 12 | # USER-MODIFIABLE MACROS 13 | # 14 | top_srcdir = . 15 | top_builddir = . 16 | 17 | CFLAGS = -O2 -march=native -g -Wall -Wextra -Wno-unused-parameter -Wno-unused-function 18 | SOFLAGS = $$(auto_soflags) 19 | LIBS = $$(auto_libs) 20 | 21 | ALL_CPPFLAGS = -I$(top_srcdir) -DWHEEL_BIT=$(WHEEL_BIT) -DWHEEL_NUM=$(WHEEL_NUM) $(CPPFLAGS) 22 | ALL_CFLAGS = $(CFLAGS) 23 | ALL_SOFLAGS = $(SOFLAGS) 24 | ALL_LDFLAGS = $(LDFLAGS) 25 | ALL_LIBS = $(LIBS) 26 | 27 | LUA_API = 5.3 28 | LUA = lua 29 | LUA51_CPPFLAGS = $(LUA_CPPFLAGS) 30 | LUA52_CPPFLAGS = $(LUA_CPPFLAGS) 31 | LUA53_CPPFLAGS = $(LUA_CPPFLAGS) 32 | 33 | WHEEL_BIT = 6 34 | WHEEL_NUM = 4 35 | 36 | RM = rm -f 37 | 38 | # END MACROS 39 | 40 | SHRC = \ 41 | top_srcdir="$(top_srcdir)"; \ 42 | top_builddir="$(top_builddir)"; \ 43 | . "$${top_srcdir}/Rules.shrc" 44 | 45 | LUA_APIS = 5.1 5.2 5.3 46 | 47 | include $(top_srcdir)/lua/Rules.mk 48 | include $(top_srcdir)/bench/Rules.mk 49 | 50 | all: test-timeout 51 | 52 | timeout.o: $(top_srcdir)/timeout.c 53 | test-timeout.o: $(top_srcdir)/test-timeout.c 54 | 55 | timeout.o test-timeout.o: 56 | @$(SHRC); echo_cmd $(CC) $(ALL_CFLAGS) -c -o $@ $${top_srcdir}/$(@F:%.o=%.c) $(ALL_CPPFLAGS) 57 | 58 | test-timeout: timeout.o test-timeout.o 59 | @$(SHRC); echo_cmd $(CC) $(ALL_CPPFLAGS) $(ALL_CFLAGS) -o $@ timeout.o test-timeout.o 60 | 61 | .PHONY: clean clean~ 62 | 63 | clean: 64 | $(RM) $(top_builddir)/test-timeout $(top_builddir)/*.o 65 | $(RM) -r $(top_builddir)/*.dSYM 66 | 67 | clean~: 68 | find $(top_builddir) $(top_srcdir) -name "*~" -exec $(RM) -- {} "+" 69 | -------------------------------------------------------------------------------- /Rules.shrc: -------------------------------------------------------------------------------- 1 | # convert to absolute paths 2 | top_srcdir="$(cd "${top_srcdir}" && pwd -L)" 3 | top_builddir="$(cd "${top_builddir}" && pwd -L)" 4 | 5 | # Paths for Lua modules (benchmarks and installed modules) 6 | export LUA_CPATH="${top_builddir}/lua/5.1/?.so;${top_builddir}/bench/?.so;;" 7 | export LUA_PATH="${top_srcdir}/lua/?.lua;${top_srcdir}/bench/?.lua;;" 8 | export LUA_CPATH_5_2="${top_builddir}/lua/5.2/?.so;${top_builddir}/bench/?.so;;" 9 | export LUA_PATH_5_2="${top_srcdir}/lua/?.lua;${top_srcdir}/bench/?.lua;;" 10 | export LUA_CPATH_5_3="${top_builddir}/lua/5.3/?.so;${top_builddir}/bench/?.so;;" 11 | export LUA_PATH_5_3="${top_srcdir}/lua/?.lua;${top_srcdir}/bench/?.lua;;" 12 | 13 | # preserve stdout so we can print commands to terminal 14 | exec 9>&1; 15 | echo_cmd() { 16 | printf "%s\n" "$*" >&9; 17 | "$@"; 18 | } 19 | 20 | auto_soflags() { 21 | case "$(uname -s)" in 22 | Darwin) 23 | printf -- "-bundle -undefined dynamic_lookup" 24 | ;; 25 | *) 26 | printf -- "-fPIC -shared" 27 | ;; 28 | esac 29 | } 30 | 31 | auto_libs() { 32 | case "$(uname -s)" in 33 | Linux) 34 | printf -- "-lrt" 35 | ;; 36 | *) 37 | ;; 38 | esac 39 | } 40 | 41 | -------------------------------------------------------------------------------- /bench/Rules.mk: -------------------------------------------------------------------------------- 1 | BENCH_MODS = bench.so $(BENCH_ALGOS:%=bench-%.so) 2 | BENCH_ALGOS = wheel heap llrb 3 | BENCH_OPS = add del expire 4 | 5 | $(top_builddir)/bench/bench.so: $(top_srcdir)/bench/bench.c 6 | $(top_builddir)/bench/bench-wheel.so: $(top_srcdir)/bench/bench-wheel.c 7 | $(top_builddir)/bench/bench-heap.so: $(top_srcdir)/bench/bench-heap.c 8 | $(top_builddir)/bench/bench-llrb.so: $(top_srcdir)/bench/bench-llrb.c 9 | 10 | $(BENCH_MODS:%=$(top_builddir)/bench/%): $(top_srcdir)/timeout.h $(top_srcdir)/timeout.c $(top_srcdir)/bench/bench.h 11 | mkdir -p $(@D) 12 | @$(SHRC); echo_cmd $(CC) -o $@ $(top_srcdir)/bench/$(@F:%.so=%.c) $(ALL_CPPFLAGS) $(ALL_CFLAGS) $(ALL_SOFLAGS) $(ALL_LDFLAGS) $(ALL_LIBS) 13 | 14 | $(BENCH_OPS:%=$(top_builddir)/bench/wheel-%.dat): $(top_builddir)/bench/bench-wheel.so $(top_builddir)/bench/bench.so $(top_srcdir)/bench/bench-aux.lua 15 | $(BENCH_OPS:%=$(top_builddir)/bench/heap-%.dat): $(top_builddir)/bench/bench-heap.so $(top_builddir)/bench/bench.so $(top_srcdir)/bench/bench-aux.lua 16 | $(BENCH_OPS:%=$(top_builddir)/bench/llrb-%.dat): $(top_builddir)/bench/bench-llrb.so $(top_builddir)/bench/bench.so $(top_srcdir)/bench/bench-aux.lua 17 | 18 | $(BENCH_ALGOS:%=$(top_builddir)/bench/%-add.dat): $(top_srcdir)/bench/bench-add.lua 19 | @$(SHRC); echo_cmd cd $(@D) && echo_cmd $(LUA) $${top_srcdir}/bench/bench-add.lua $${top_builddir}/bench/bench-$(@F:%-add.dat=%).so > $(@F).tmp 20 | mv $@.tmp $@ 21 | 22 | $(BENCH_ALGOS:%=$(top_builddir)/bench/%-del.dat): $(top_srcdir)/bench/bench-del.lua 23 | @$(SHRC); echo_cmd cd $(@D) && echo_cmd $(LUA) $${top_srcdir}/bench/bench-del.lua $${top_builddir}/bench/bench-$(@F:%-del.dat=%).so > $(@F).tmp 24 | mv $@.tmp $@ 25 | 26 | $(BENCH_ALGOS:%=$(top_builddir)/bench/%-expire.dat): $(top_srcdir)/bench/bench-expire.lua 27 | @$(SHRC); echo_cmd cd $(@D) && echo_cmd $(LUA) $${top_srcdir}/bench/bench-expire.lua $${top_builddir}/bench/bench-$(@F:%-expire.dat=%).so > $(@F).tmp 28 | mv $@.tmp $@ 29 | 30 | $(top_builddir)/bench/bench.eps: \ 31 | $(BENCH_OPS:%=$(top_builddir)/bench/wheel-%.dat) \ 32 | $(BENCH_OPS:%=$(top_builddir)/bench/heap-%.dat) 33 | # $(BENCH_OPS:%=$(top_builddir)/bench/llrb-%.dat) 34 | 35 | $(top_builddir)/bench/bench.eps: $(top_srcdir)/bench/bench.plt 36 | @$(SHRC); echo_cmd cd $(@D) && echo_cmd gnuplot $${top_srcdir}/bench/bench.plt > $(@F).tmp 37 | mv $@.tmp $@ 38 | 39 | $(top_builddir)/bench/bench.pdf: $(top_builddir)/bench/bench.eps 40 | @$(SHRC); echo_cmd ps2pdf $${top_builddir}/bench/bench.eps $@ 41 | 42 | bench-mods: $(BENCH_MODS:%=$(top_builddir)/bench/%) 43 | 44 | bench-all: $(top_builddir)/bench/bench.pdf 45 | 46 | bench-clean: 47 | $(RM) -r $(top_builddir)/bench/*.so $(top_builddir)/bench/*.dSYM 48 | $(RM) $(top_builddir)/bench/*.dat $(top_builddir)/bench/*.tmp 49 | $(RM) $(top_builddir)/bench/bench.{eps,pdf} 50 | -------------------------------------------------------------------------------- /bench/bench-add.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env lua 2 | 3 | local bench = require"bench" 4 | local aux = require"bench-aux" 5 | 6 | local lib = ... or aux.optenv("BENCH_L", "bench-wheel.so") 7 | local limit = tonumber(aux.optenv("BENCH_N", 1000000)) 8 | local step = tonumber(aux.optenv("BENCH_S", limit / 100)) 9 | local exp_step = tonumber(aux.optenv("BENCH_E", 1.0)) 10 | local verbose = aux.toboolean(os.getenv("BENCH_V", false)) 11 | 12 | local B = bench.new(lib, count, nil, verbose) 13 | local fill_count, fill_last = B:fill(limit) 14 | 15 | for i=0,limit,step do 16 | local exp_elapsed, fill_elapsed, fill_rate 17 | 18 | -- expire all timeouts 19 | --exp_elapsed = aux.time(B.expire, B, fill_count, fill_last * exp_step) 20 | exp_elapsed = aux.time(B.del, B, 0, fill_count) 21 | assert(B:empty()) 22 | 23 | -- add i timeouts 24 | fill_elapsed, fill_count, fill_last = aux.time(B.fill, B, i) 25 | assert(fill_count == i) 26 | fill_rate = fill_elapsed > 0 and (fill_count / fill_elapsed) or 0 27 | 28 | local fmt = verbose and "%d\t%f\t(%d/s)\t(exp:%f)" or "%d\t%f" 29 | aux.say(fmt, i, fill_elapsed, fill_rate, exp_elapsed) 30 | end 31 | -------------------------------------------------------------------------------- /bench/bench-aux.lua: -------------------------------------------------------------------------------- 1 | local bench = require"bench" 2 | local clock = bench.clock 3 | 4 | local aux = {} 5 | 6 | local function time_return(begun, ...) 7 | local duration = clock() - begun 8 | return duration, ... 9 | end 10 | 11 | function aux.time(f, ...) 12 | local begun = clock() 13 | return time_return(begun, f(...)) 14 | end 15 | 16 | function aux.say(...) 17 | print(string.format(...)) 18 | end 19 | 20 | function aux.toboolean(s) 21 | return tostring(s):match("^[1TtYy]") and true or false 22 | end 23 | 24 | function aux.optenv(k, def) 25 | local s = os.getenv(k) 26 | 27 | return (s and #s > 0 and s) or def 28 | end 29 | 30 | return aux 31 | -------------------------------------------------------------------------------- /bench/bench-del.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env lua 2 | 3 | local bench = require"bench" 4 | local aux = require"bench-aux" 5 | 6 | local lib = ... or aux.optenv("BENCH_L", "bench-wheel.so") 7 | local limit = tonumber(aux.optenv("BENCH_N", 1000000)) 8 | local step = tonumber(aux.optenv("BENCH_S", limit / 100)) 9 | local verbose = aux.toboolean(os.getenv("BENCH_V", false)) 10 | 11 | local B = bench.new(lib, count) 12 | 13 | for i=0,limit,step do 14 | -- add i timeouts 15 | local fill_elapsed, fill_count = aux.time(B.fill, B, i, 60 * 1000000) 16 | assert(i == fill_count) 17 | 18 | --- delete i timeouts 19 | local del_elapsed = aux.time(B.del, B, 0, fill_count) 20 | assert(B:empty()) 21 | local del_rate = i > 0 and i / del_elapsed or 0 22 | 23 | local fmt = verbose and "%d\t%f\t(%d/s)\t(fill:%f)" or "%d\t%f" 24 | aux.say(fmt, i, del_elapsed, del_rate, fill_elapsed) 25 | end 26 | -------------------------------------------------------------------------------- /bench/bench-expire.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env lua 2 | 3 | local bench = require"bench" 4 | local aux = require"bench-aux" 5 | 6 | local lib = ... or aux.optenv("BENCH_L", "bench-wheel.so") 7 | local limit = tonumber(aux.optenv("BENCH_N", 1000000)) 8 | local step = tonumber(aux.optenv("BENCH_S", limit / 100)) 9 | -- expire 1/1000 * #timeouts per clock update 10 | local exp_step = tonumber(aux.optenv("BENCH_E", 0.0001)) 11 | local verbose = aux.toboolean(os.getenv("BENCH_V", false)) 12 | 13 | local B = require"bench".new(lib, count) 14 | 15 | for i=0,limit,step do 16 | -- add i timeouts 17 | local fill_elapsed, fill_count, fill_last = aux.time(B.fill, B, i) 18 | 19 | -- expire timeouts by iteratively updating clock. exp_step is the 20 | -- approximate number of timeouts (as a fraction of the total number 21 | -- of timeouts) that will expire per update. 22 | local exp_elapsed, exp_count = aux.time(B.expire, B, fill_count, math.floor(fill_last * exp_step)) 23 | assert(exp_count == i) 24 | assert(B:empty()) 25 | local exp_rate = i > 0 and i / exp_elapsed or 0 26 | 27 | local fmt = verbose and "%d\t%f\t(%d/s)\t(fill:%f)" or "%d\t%f" 28 | aux.say(fmt, i, exp_elapsed, exp_rate, fill_elapsed) 29 | end 30 | -------------------------------------------------------------------------------- /bench/bench-heap.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2006 Maxim Yegorushkin 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 3. The name of the author may not be used to endorse or promote products 14 | * derived from this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 17 | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 18 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 19 | * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 20 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 21 | * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 22 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 23 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 25 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | #ifndef _MIN_HEAP_H_ 28 | #define _MIN_HEAP_H_ 29 | 30 | #include 31 | #include 32 | #include "timeout.h" 33 | #include "bench.h" 34 | 35 | #define min_heap_idx interval 36 | 37 | typedef timeout_t min_heap_idx_t; 38 | 39 | typedef struct min_heap 40 | { 41 | struct timeout** p; 42 | unsigned n, a; 43 | timeout_t curtime; 44 | } min_heap_t; 45 | 46 | static inline void min_heap_ctor(min_heap_t* s); 47 | static inline void min_heap_dtor(min_heap_t* s); 48 | static inline void min_heap_elem_init(struct timeout* e); 49 | static inline int min_heap_elem_greater(struct timeout *a, struct timeout *b); 50 | static inline int min_heap_empty(min_heap_t* s); 51 | static inline unsigned min_heap_size(min_heap_t* s); 52 | static inline struct timeout* min_heap_top(min_heap_t* s); 53 | static inline int min_heap_reserve(min_heap_t* s, unsigned n); 54 | static inline int min_heap_push(min_heap_t* s, struct timeout* e); 55 | static inline struct timeout* min_heap_pop(min_heap_t* s); 56 | static inline int min_heap_erase(min_heap_t* s, struct timeout* e); 57 | static inline void min_heap_shift_up_(min_heap_t* s, unsigned hole_index, struct timeout* e); 58 | static inline void min_heap_shift_down_(min_heap_t* s, unsigned hole_index, struct timeout* e); 59 | 60 | int min_heap_elem_greater(struct timeout *a, struct timeout *b) 61 | { 62 | return a->expires > b->expires; 63 | } 64 | 65 | void min_heap_ctor(min_heap_t* s) { s->p = 0; s->n = 0; s->a = 0; } 66 | void min_heap_dtor(min_heap_t* s) { if(s->p) free(s->p); } 67 | void min_heap_elem_init(struct timeout* e) { e->min_heap_idx = -1; } 68 | int min_heap_empty(min_heap_t* s) { return 0u == s->n; } 69 | unsigned min_heap_size(min_heap_t* s) { return s->n; } 70 | struct timeout* min_heap_top(min_heap_t* s) { return s->n ? *s->p : 0; } 71 | 72 | int min_heap_push(min_heap_t* s, struct timeout* e) 73 | { 74 | if(min_heap_reserve(s, s->n + 1)) 75 | return -1; 76 | min_heap_shift_up_(s, s->n++, e); 77 | return 0; 78 | } 79 | 80 | struct timeout* min_heap_pop(min_heap_t* s) 81 | { 82 | if(s->n) 83 | { 84 | struct timeout* e = *s->p; 85 | min_heap_shift_down_(s, 0u, s->p[--s->n]); 86 | e->min_heap_idx = -1; 87 | return e; 88 | } 89 | return 0; 90 | } 91 | 92 | int min_heap_erase(min_heap_t* s, struct timeout* e) 93 | { 94 | if(((min_heap_idx_t)-1) != e->min_heap_idx) 95 | { 96 | struct timeout *last = s->p[--s->n]; 97 | unsigned parent = (e->min_heap_idx - 1) / 2; 98 | /* we replace e with the last element in the heap. We might need to 99 | shift it upward if it is less than its parent, or downward if it is 100 | greater than one or both its children. Since the children are known 101 | to be less than the parent, it can't need to shift both up and 102 | down. */ 103 | if (e->min_heap_idx > 0 && min_heap_elem_greater(s->p[parent], last)) 104 | min_heap_shift_up_(s, e->min_heap_idx, last); 105 | else 106 | min_heap_shift_down_(s, e->min_heap_idx, last); 107 | e->min_heap_idx = -1; 108 | return 0; 109 | } 110 | return -1; 111 | } 112 | 113 | int min_heap_reserve(min_heap_t* s, unsigned n) 114 | { 115 | if(s->a < n) 116 | { 117 | struct timeout** p; 118 | unsigned a = s->a ? s->a * 2 : 8; 119 | if(a < n) 120 | a = n; 121 | if(!(p = (struct timeout**)realloc(s->p, a * sizeof *p))) 122 | return -1; 123 | s->p = p; 124 | s->a = a; 125 | } 126 | return 0; 127 | } 128 | 129 | void min_heap_shift_up_(min_heap_t* s, unsigned hole_index, struct timeout* e) 130 | { 131 | unsigned parent = (hole_index - 1) / 2; 132 | while(hole_index && min_heap_elem_greater(s->p[parent], e)) 133 | { 134 | (s->p[hole_index] = s->p[parent])->min_heap_idx = hole_index; 135 | hole_index = parent; 136 | parent = (hole_index - 1) / 2; 137 | } 138 | (s->p[hole_index] = e)->min_heap_idx = hole_index; 139 | } 140 | 141 | void min_heap_shift_down_(min_heap_t* s, unsigned hole_index, struct timeout* e) 142 | { 143 | unsigned min_child = 2 * (hole_index + 1); 144 | while(min_child <= s->n) 145 | { 146 | min_child -= min_child == s->n || min_heap_elem_greater(s->p[min_child], s->p[min_child - 1]); 147 | if(!(min_heap_elem_greater(e, s->p[min_child]))) 148 | break; 149 | (s->p[hole_index] = s->p[min_child])->min_heap_idx = hole_index; 150 | hole_index = min_child; 151 | min_child = 2 * (hole_index + 1); 152 | } 153 | min_heap_shift_up_(s, hole_index, e); 154 | } 155 | 156 | #endif /* _MIN_HEAP_H_ */ 157 | 158 | 159 | static void *init(struct timeout *timeout, size_t count, int verbose) { 160 | min_heap_t *H; 161 | size_t i; 162 | 163 | H = calloc(1, sizeof *H); 164 | 165 | min_heap_ctor(H); 166 | if (0 != min_heap_reserve(H, count)) 167 | err(1, "realloc"); 168 | 169 | for (i = 0; i < count; i++) { 170 | min_heap_elem_init(&timeout[i]); 171 | } 172 | 173 | return H; 174 | } /* init() */ 175 | 176 | 177 | static void add(void *ctx, struct timeout *to, timeout_t expires) { 178 | min_heap_t *H = ctx; 179 | min_heap_erase(H, to); 180 | to->expires = H->curtime + expires; 181 | if (0 != min_heap_push(H, to)) 182 | err(1, "realloc"); 183 | } /* add() */ 184 | 185 | 186 | static void del(void *ctx, struct timeout *to) { 187 | min_heap_erase(ctx, to); 188 | } /* del() */ 189 | 190 | 191 | static struct timeout *get(void *ctx) { 192 | min_heap_t *H = ctx; 193 | struct timeout *to; 194 | 195 | if ((to = min_heap_top(H)) && to->expires <= H->curtime) 196 | return min_heap_pop(H); 197 | 198 | return NULL; 199 | } /* get() */ 200 | 201 | 202 | static void update(void *ctx, timeout_t ts) { 203 | min_heap_t *H = ctx; 204 | H->curtime = ts; 205 | } /* update() */ 206 | 207 | 208 | static void check(void *ctx) { 209 | return; 210 | } /* check() */ 211 | 212 | 213 | static int empty(void *ctx) { 214 | min_heap_t *H = ctx; 215 | 216 | return (NULL == min_heap_top(H)); 217 | } /* empty() */ 218 | 219 | 220 | static void destroy(void *H) { 221 | free(H); 222 | return; 223 | } /* destroy() */ 224 | 225 | 226 | const struct benchops benchops = { 227 | .init = &init, 228 | .add = &add, 229 | .del = &del, 230 | .get = &get, 231 | .update = &update, 232 | .check = &check, 233 | .empty = &empty, 234 | .destroy = &destroy, 235 | }; 236 | 237 | -------------------------------------------------------------------------------- /bench/bench-llrb.c: -------------------------------------------------------------------------------- 1 | /* ========================================================================== 2 | * llrb.h - Iterative Left-leaning Red-Black Tree. 3 | * -------------------------------------------------------------------------- 4 | * Copyright (c) 2011, 2013 William Ahern 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a 7 | * copy of this software and associated documentation files (the 8 | * "Software"), to deal in the Software without restriction, including 9 | * without limitation the rights to use, copy, modify, merge, publish, 10 | * distribute, sublicense, and/or sell copies of the Software, and to permit 11 | * persons to whom the Software is furnished to do so, subject to the 12 | * following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included 15 | * in all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 18 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 20 | * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 21 | * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 22 | * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 23 | * USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | * -------------------------------------------------------------------------- 25 | * CREDITS: 26 | * o Algorithm courtesy of Robert Sedgewick, "Left-leaning Red-Black 27 | * Trees" (September 2008); and Robert Sedgewick and Kevin Wayne, 28 | * Algorithms (4th ed. 2011). 29 | * 30 | * Sedgewick touts the simplicity of the recursive implementation, 31 | * but at least for the 2-3 tree variant the iterative approach is 32 | * almost line-for-line identical. The magic of C pointers helps; 33 | * it'd be uglier with Java. 34 | * 35 | * A couple of missing NULL checks were added to Sedgewick's deletion 36 | * example, and insert was optimized to short-circuit rotations when 37 | * walking up the tree. 38 | * 39 | * o Code implemented in the fashion of Niels Provos' excellent *BSD 40 | * sys/tree.h pre-processor library. 41 | * 42 | * Regarding relative performance, I've refrained from sharing my own 43 | * benchmarks. Differences in run-time speed were too correlated to 44 | * compiler options and other external factors. 45 | * 46 | * Provos' delete implementation doesn't need to start at the root of 47 | * the tree. However, RB_REMOVE must be passed the actual node to be 48 | * removed. LLRB_REMOVE merely requires a key, much like 49 | * RB_FIND/LLRB_FIND. 50 | * ========================================================================== 51 | */ 52 | #ifndef LLRB_H 53 | #define LLRB_H 54 | 55 | #define LLRB_VENDOR "william@25thandClement.com" 56 | #define LLRB_VERSION 0x20130925 57 | 58 | #ifndef LLRB_STATIC 59 | #ifdef __GNUC__ 60 | #define LLRB_STATIC __attribute__((__unused__)) static 61 | #else 62 | #define LLRB_STATIC static 63 | #endif 64 | #endif 65 | 66 | #define LLRB_HEAD(name, type) \ 67 | struct name { struct type *rbh_root; } 68 | 69 | #define LLRB_INITIALIZER(root) { 0 } 70 | 71 | #define LLRB_INIT(root) do { (root)->rbh_root = 0; } while (0) 72 | 73 | #define LLRB_BLACK 0 74 | #define LLRB_RED 1 75 | 76 | #define LLRB_ENTRY(type) \ 77 | struct { struct type *rbe_left, *rbe_right, *rbe_parent; _Bool rbe_color; } 78 | 79 | #define LLRB_LEFT(elm, field) (elm)->field.rbe_left 80 | #define LLRB_RIGHT(elm, field) (elm)->field.rbe_right 81 | #define LLRB_PARENT(elm, field) (elm)->field.rbe_parent 82 | #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)) 83 | #define LLRB_COLOR(elm, field) (elm)->field.rbe_color 84 | #define LLRB_ROOT(head) (head)->rbh_root 85 | #define LLRB_EMPTY(head) ((head)->rbh_root == 0) 86 | #define LLRB_ISRED(elm, field) ((elm) && LLRB_COLOR((elm), field) == LLRB_RED) 87 | 88 | #define LLRB_PROTOTYPE(name, type, field, cmp) \ 89 | LLRB_PROTOTYPE_INTERNAL(name, type, field, cmp,) 90 | #define LLRB_PROTOTYPE_STATIC(name, type, field, cmp) \ 91 | LLRB_PROTOTYPE_INTERNAL(name, type, field, cmp, LLRB_STATIC) 92 | #define LLRB_PROTOTYPE_INTERNAL(name, type, field, cmp, attr) \ 93 | attr struct type *name##_LLRB_INSERT(struct name *, struct type *); \ 94 | attr struct type *name##_LLRB_DELETE(struct name *, struct type *); \ 95 | attr struct type *name##_LLRB_FIND(struct name *, struct type *); \ 96 | attr struct type *name##_LLRB_MIN(struct type *); \ 97 | attr struct type *name##_LLRB_MAX(struct type *); \ 98 | attr struct type *name##_LLRB_NEXT(struct type *); 99 | 100 | #define LLRB_GENERATE(name, type, field, cmp) \ 101 | LLRB_GENERATE_INTERNAL(name, type, field, cmp,) 102 | #define LLRB_GENERATE_STATIC(name, type, field, cmp) \ 103 | LLRB_GENERATE_INTERNAL(name, type, field, cmp, LLRB_STATIC) 104 | #define LLRB_GENERATE_INTERNAL(name, type, field, cmp, attr) \ 105 | static inline void name##_LLRB_ROTL(struct type **pivot) { \ 106 | struct type *a = *pivot; \ 107 | struct type *b = LLRB_RIGHT(a, field); \ 108 | if ((LLRB_RIGHT(a, field) = LLRB_LEFT(b, field))) \ 109 | LLRB_PARENT(LLRB_RIGHT(a, field), field) = a; \ 110 | LLRB_LEFT(b, field) = a; \ 111 | LLRB_COLOR(b, field) = LLRB_COLOR(a, field); \ 112 | LLRB_COLOR(a, field) = LLRB_RED; \ 113 | LLRB_PARENT(b, field) = LLRB_PARENT(a, field); \ 114 | LLRB_PARENT(a, field) = b; \ 115 | *pivot = b; \ 116 | } \ 117 | static inline void name##_LLRB_ROTR(struct type **pivot) { \ 118 | struct type *b = *pivot; \ 119 | struct type *a = LLRB_LEFT(b, field); \ 120 | if ((LLRB_LEFT(b, field) = LLRB_RIGHT(a, field))) \ 121 | LLRB_PARENT(LLRB_LEFT(b, field), field) = b; \ 122 | LLRB_RIGHT(a, field) = b; \ 123 | LLRB_COLOR(a, field) = LLRB_COLOR(b, field); \ 124 | LLRB_COLOR(b, field) = LLRB_RED; \ 125 | LLRB_PARENT(a, field) = LLRB_PARENT(b, field); \ 126 | LLRB_PARENT(b, field) = a; \ 127 | *pivot = a; \ 128 | } \ 129 | static inline void name##_LLRB_FLIP(struct type *root) { \ 130 | LLRB_COLOR(root, field) = !LLRB_COLOR(root, field); \ 131 | LLRB_COLOR(LLRB_LEFT(root, field), field) = !LLRB_COLOR(LLRB_LEFT(root, field), field); \ 132 | LLRB_COLOR(LLRB_RIGHT(root, field), field) = !LLRB_COLOR(LLRB_RIGHT(root, field), field); \ 133 | } \ 134 | static inline void name##_LLRB_FIXUP(struct type **root) { \ 135 | if (LLRB_ISRED(LLRB_RIGHT(*root, field), field) && !LLRB_ISRED(LLRB_LEFT(*root, field), field)) \ 136 | name##_LLRB_ROTL(root); \ 137 | if (LLRB_ISRED(LLRB_LEFT(*root, field), field) && LLRB_ISRED(LLRB_LEFT(LLRB_LEFT(*root, field), field), field)) \ 138 | name##_LLRB_ROTR(root); \ 139 | if (LLRB_ISRED(LLRB_LEFT(*root, field), field) && LLRB_ISRED(LLRB_RIGHT(*root, field), field)) \ 140 | name##_LLRB_FLIP(*root); \ 141 | } \ 142 | attr struct type *name##_LLRB_INSERT(struct name *head, struct type *elm) { \ 143 | struct type **root = &LLRB_ROOT(head); \ 144 | struct type *parent = 0; \ 145 | while (*root) { \ 146 | int comp = (cmp)((elm), (*root)); \ 147 | parent = *root; \ 148 | if (comp < 0) \ 149 | root = &LLRB_LEFT(*root, field); \ 150 | else if (comp > 0) \ 151 | root = &LLRB_RIGHT(*root, field); \ 152 | else \ 153 | return *root; \ 154 | } \ 155 | LLRB_LEFT((elm), field) = 0; \ 156 | LLRB_RIGHT((elm), field) = 0; \ 157 | LLRB_COLOR((elm), field) = LLRB_RED; \ 158 | LLRB_PARENT((elm), field) = parent; \ 159 | *root = (elm); \ 160 | while (parent && (LLRB_ISRED(LLRB_LEFT(parent, field), field) || LLRB_ISRED(LLRB_RIGHT(parent, field), field))) { \ 161 | root = LLRB_EDGE(head, parent, field); \ 162 | parent = LLRB_PARENT(parent, field); \ 163 | name##_LLRB_FIXUP(root); \ 164 | } \ 165 | LLRB_COLOR(LLRB_ROOT(head), field) = LLRB_BLACK; \ 166 | return 0; \ 167 | } \ 168 | static inline void name##_LLRB_MOVL(struct type **pivot) { \ 169 | name##_LLRB_FLIP(*pivot); \ 170 | if (LLRB_ISRED(LLRB_LEFT(LLRB_RIGHT(*pivot, field), field), field)) { \ 171 | name##_LLRB_ROTR(&LLRB_RIGHT(*pivot, field)); \ 172 | name##_LLRB_ROTL(pivot); \ 173 | name##_LLRB_FLIP(*pivot); \ 174 | } \ 175 | } \ 176 | static inline void name##_LLRB_MOVR(struct type **pivot) { \ 177 | name##_LLRB_FLIP(*pivot); \ 178 | if (LLRB_ISRED(LLRB_LEFT(LLRB_LEFT(*pivot, field), field), field)) { \ 179 | name##_LLRB_ROTR(pivot); \ 180 | name##_LLRB_FLIP(*pivot); \ 181 | } \ 182 | } \ 183 | static inline struct type *name##_DELETEMIN(struct name *head, struct type **root) { \ 184 | struct type **pivot = root, *deleted, *parent; \ 185 | while (LLRB_LEFT(*pivot, field)) { \ 186 | if (!LLRB_ISRED(LLRB_LEFT(*pivot, field), field) && !LLRB_ISRED(LLRB_LEFT(LLRB_LEFT(*pivot, field), field), field)) \ 187 | name##_LLRB_MOVL(pivot); \ 188 | pivot = &LLRB_LEFT(*pivot, field); \ 189 | } \ 190 | deleted = *pivot; \ 191 | parent = LLRB_PARENT(*pivot, field); \ 192 | *pivot = 0; \ 193 | while (root != pivot) { \ 194 | pivot = LLRB_EDGE(head, parent, field); \ 195 | parent = LLRB_PARENT(parent, field); \ 196 | name##_LLRB_FIXUP(pivot); \ 197 | } \ 198 | return deleted; \ 199 | } \ 200 | attr struct type *name##_LLRB_DELETE(struct name *head, struct type *elm) { \ 201 | struct type **root = &LLRB_ROOT(head), *parent = 0, *deleted = 0; \ 202 | int comp; \ 203 | while (*root) { \ 204 | parent = LLRB_PARENT(*root, field); \ 205 | comp = (cmp)(elm, *root); \ 206 | if (comp < 0) { \ 207 | if (LLRB_LEFT(*root, field) && !LLRB_ISRED(LLRB_LEFT(*root, field), field) && !LLRB_ISRED(LLRB_LEFT(LLRB_LEFT(*root, field), field), field)) \ 208 | name##_LLRB_MOVL(root); \ 209 | root = &LLRB_LEFT(*root, field); \ 210 | } else { \ 211 | if (LLRB_ISRED(LLRB_LEFT(*root, field), field)) { \ 212 | name##_LLRB_ROTR(root); \ 213 | comp = (cmp)(elm, *root); \ 214 | } \ 215 | if (!comp && !LLRB_RIGHT(*root, field)) { \ 216 | deleted = *root; \ 217 | *root = 0; \ 218 | break; \ 219 | } \ 220 | if (LLRB_RIGHT(*root, field) && !LLRB_ISRED(LLRB_RIGHT(*root, field), field) && !LLRB_ISRED(LLRB_LEFT(LLRB_RIGHT(*root, field), field), field)) { \ 221 | name##_LLRB_MOVR(root); \ 222 | comp = (cmp)(elm, *root); \ 223 | } \ 224 | if (!comp) { \ 225 | struct type *orphan = name##_DELETEMIN(head, &LLRB_RIGHT(*root, field)); \ 226 | LLRB_COLOR(orphan, field) = LLRB_COLOR(*root, field); \ 227 | LLRB_PARENT(orphan, field) = LLRB_PARENT(*root, field); \ 228 | if ((LLRB_RIGHT(orphan, field) = LLRB_RIGHT(*root, field))) \ 229 | LLRB_PARENT(LLRB_RIGHT(orphan, field), field) = orphan; \ 230 | if ((LLRB_LEFT(orphan, field) = LLRB_LEFT(*root, field))) \ 231 | LLRB_PARENT(LLRB_LEFT(orphan, field), field) = orphan; \ 232 | deleted = *root; \ 233 | *root = orphan; \ 234 | parent = *root; \ 235 | break; \ 236 | } else \ 237 | root = &LLRB_RIGHT(*root, field); \ 238 | } \ 239 | } \ 240 | while (parent) { \ 241 | root = LLRB_EDGE(head, parent, field); \ 242 | parent = LLRB_PARENT(parent, field); \ 243 | name##_LLRB_FIXUP(root); \ 244 | } \ 245 | if (LLRB_ROOT(head)) \ 246 | LLRB_COLOR(LLRB_ROOT(head), field) = LLRB_BLACK; \ 247 | return deleted; \ 248 | } \ 249 | attr struct type *name##_LLRB_FIND(struct name *head, struct type *key) { \ 250 | struct type *elm = LLRB_ROOT(head); \ 251 | while (elm) { \ 252 | int comp = (cmp)(key, elm); \ 253 | if (comp < 0) \ 254 | elm = LLRB_LEFT(elm, field); \ 255 | else if (comp > 0) \ 256 | elm = LLRB_RIGHT(elm, field); \ 257 | else \ 258 | return elm; \ 259 | } \ 260 | return 0; \ 261 | } \ 262 | attr struct type *name##_LLRB_MIN(struct type *elm) { \ 263 | while (elm && LLRB_LEFT(elm, field)) \ 264 | elm = LLRB_LEFT(elm, field); \ 265 | return elm; \ 266 | } \ 267 | attr struct type *name##_LLRB_MAX(struct type *elm) { \ 268 | while (elm && LLRB_RIGHT(elm, field)) \ 269 | elm = LLRB_RIGHT(elm, field); \ 270 | return elm; \ 271 | } \ 272 | attr struct type *name##_LLRB_NEXT(struct type *elm) { \ 273 | if (LLRB_RIGHT(elm, field)) { \ 274 | return name##_LLRB_MIN(LLRB_RIGHT(elm, field)); \ 275 | } else if (LLRB_PARENT(elm, field)) { \ 276 | if (elm == LLRB_LEFT(LLRB_PARENT(elm, field), field)) \ 277 | return LLRB_PARENT(elm, field); \ 278 | while (LLRB_PARENT(elm, field) && elm == LLRB_RIGHT(LLRB_PARENT(elm, field), field)) \ 279 | elm = LLRB_PARENT(elm, field); \ 280 | return LLRB_PARENT(elm, field); \ 281 | } else return 0; \ 282 | } 283 | 284 | #define LLRB_INSERT(name, head, elm) name##_LLRB_INSERT((head), (elm)) 285 | #define LLRB_DELETE(name, head, elm) name##_LLRB_DELETE((head), (elm)) 286 | #define LLRB_REMOVE(name, head, elm) name##_LLRB_DELETE((head), (elm)) 287 | #define LLRB_FIND(name, head, elm) name##_LLRB_FIND((head), (elm)) 288 | #define LLRB_MIN(name, head) name##_LLRB_MIN(LLRB_ROOT((head))) 289 | #define LLRB_MAX(name, head) name##_LLRB_MAX(LLRB_ROOT((head))) 290 | #define LLRB_NEXT(name, head, elm) name##_LLRB_NEXT((elm)) 291 | 292 | #define LLRB_FOREACH(elm, name, head) \ 293 | for ((elm) = LLRB_MIN(name, head); (elm); (elm) = name##_LLRB_NEXT((elm))) 294 | 295 | #endif /* LLRB_H */ 296 | 297 | 298 | #include 299 | 300 | #include "timeout.h" 301 | #include "bench.h" 302 | 303 | 304 | struct rbtimeout { 305 | timeout_t expires; 306 | 307 | int pending; 308 | 309 | LLRB_ENTRY(rbtimeout) rbe; 310 | }; 311 | 312 | struct rbtimeouts { 313 | timeout_t curtime; 314 | LLRB_HEAD(tree, rbtimeout) tree; 315 | }; 316 | 317 | 318 | static int timeoutcmp(struct rbtimeout *a, struct rbtimeout *b) { 319 | if (a->expires < b->expires) { 320 | return -1; 321 | } else if (a->expires > b->expires) { 322 | return 1; 323 | } else if (a < b) { 324 | return -1; 325 | } else if (a > b) { 326 | return 1; 327 | } else { 328 | return 0; 329 | } 330 | } /* timeoutcmp() */ 331 | 332 | LLRB_GENERATE_STATIC(tree, rbtimeout, rbe, timeoutcmp) 333 | 334 | static void *init(struct timeout *timeout, size_t count, int verbose) { 335 | struct rbtimeouts *T; 336 | size_t i; 337 | 338 | T = malloc(sizeof *T); 339 | T->curtime = 0; 340 | LLRB_INIT(&T->tree); 341 | 342 | for (i = 0; i < count; i++) { 343 | struct rbtimeout *to = (void *)&timeout[i]; 344 | to->expires = 0; 345 | to->pending = 0; 346 | } 347 | 348 | return T; 349 | } /* init() */ 350 | 351 | 352 | static void add(void *ctx, struct timeout *_to, timeout_t expires) { 353 | struct rbtimeouts *T = ctx; 354 | struct rbtimeout *to = (void *)_to; 355 | 356 | if (to->pending) 357 | LLRB_REMOVE(tree, &T->tree, to); 358 | 359 | to->expires = T->curtime + expires; 360 | LLRB_INSERT(tree, &T->tree, to); 361 | to->pending = 1; 362 | } /* add() */ 363 | 364 | 365 | static void del(void *ctx, struct timeout *_to) { 366 | struct rbtimeouts *T = ctx; 367 | struct rbtimeout *to = (void *)_to; 368 | 369 | LLRB_REMOVE(tree, &T->tree, to); 370 | to->pending = 0; 371 | to->expires = 0; 372 | } /* del() */ 373 | 374 | 375 | static struct timeout *get(void *ctx) { 376 | struct rbtimeouts *T = ctx; 377 | struct rbtimeout *to; 378 | 379 | if ((to = LLRB_MIN(tree, &T->tree)) && to->expires <= T->curtime) { 380 | LLRB_REMOVE(tree, &T->tree, to); 381 | to->pending = 0; 382 | to->expires = 0; 383 | 384 | return (void *)to; 385 | } 386 | 387 | return NULL; 388 | } /* get() */ 389 | 390 | 391 | static void update(void *ctx, timeout_t ts) { 392 | struct rbtimeouts *T = ctx; 393 | T->curtime = ts; 394 | } /* update() */ 395 | 396 | 397 | static void check(void *ctx) { 398 | return; 399 | } /* check() */ 400 | 401 | 402 | static int empty(void *ctx) { 403 | struct rbtimeouts *T = ctx; 404 | 405 | return LLRB_EMPTY(&T->tree); 406 | } /* empty() */ 407 | 408 | 409 | static void destroy(void *ctx) { 410 | free(ctx); 411 | return; 412 | } /* destroy() */ 413 | 414 | 415 | const struct benchops benchops = { 416 | .init = &init, 417 | .add = &add, 418 | .del = &del, 419 | .get = &get, 420 | .update = &update, 421 | .check = &check, 422 | .empty = &empty, 423 | .destroy = &destroy, 424 | }; 425 | 426 | -------------------------------------------------------------------------------- /bench/bench-wheel.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define TIMEOUT_PUBLIC static 4 | 5 | #include "timeout.h" 6 | #include "timeout.c" 7 | #include "bench.h" 8 | 9 | 10 | static void *init(struct timeout *timeout, size_t count, int verbose) { 11 | struct timeouts *T; 12 | size_t i; 13 | int error; 14 | 15 | T = timeouts_open(TIMEOUT_mHZ, &error); 16 | 17 | for (i = 0; i < count; i++) { 18 | timeout_init(&timeout[i], 0); 19 | } 20 | 21 | #if TIMEOUT_DEBUG - 0 22 | timeout_debug = verbose; 23 | #endif 24 | 25 | return T; 26 | } /* init() */ 27 | 28 | 29 | static void add(void *T, struct timeout *to, timeout_t expires) { 30 | timeouts_add(T, to, expires); 31 | } /* add() */ 32 | 33 | 34 | static void del(void *T, struct timeout *to) { 35 | timeouts_del(T, to); 36 | } /* del() */ 37 | 38 | 39 | static struct timeout *get(void *T) { 40 | return timeouts_get(T); 41 | } /* get() */ 42 | 43 | 44 | static void update(void *T, timeout_t ts) { 45 | timeouts_update(T, ts); 46 | } /* update() */ 47 | 48 | 49 | static void (check)(void *T) { 50 | if (!timeouts_check(T, stderr)) 51 | _Exit(1); 52 | } /* check() */ 53 | 54 | 55 | static int empty(void *T) { 56 | return !(timeouts_pending(T) || timeouts_expired(T)); 57 | } /* empty() */ 58 | 59 | 60 | static struct timeout *next(void *T, struct timeouts_it *it) { 61 | return timeouts_next(T, it); 62 | } /* next() */ 63 | 64 | 65 | static void destroy(void *T) { 66 | timeouts_close(T); 67 | } /* destroy() */ 68 | 69 | 70 | const struct benchops benchops = { 71 | .init = &init, 72 | .add = &add, 73 | .del = &del, 74 | .get = &get, 75 | .update = &update, 76 | .check = &check, 77 | .empty = &empty, 78 | .next = &next, 79 | .destroy = &destroy 80 | }; 81 | 82 | -------------------------------------------------------------------------------- /bench/bench.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #if __APPLE__ 9 | #include 10 | #endif 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | #include "timeout.h" 17 | #include "bench.h" 18 | 19 | #if LUA_VERSION_NUM < 502 20 | static int lua_absindex(lua_State *L, int idx) { 21 | return (idx > 0 || idx <= LUA_REGISTRYINDEX)? idx : lua_gettop(L) + idx + 1; 22 | } /* lua_absindex() */ 23 | 24 | static void luaL_setfuncs(lua_State *L, const luaL_Reg *l, int nup) { 25 | int i, t = lua_absindex(L, -1 - nup); 26 | 27 | for (; l->name; l++) { 28 | for (i = 0; i < nup; i++) 29 | lua_pushvalue(L, -nup); 30 | lua_pushcclosure(L, l->func, nup); 31 | lua_setfield(L, t, l->name); 32 | } 33 | 34 | lua_pop(L, nup); 35 | } /* luaL_setfuncs() */ 36 | 37 | #define luaL_newlibtable(L, l) \ 38 | lua_createtable(L, 0, (sizeof (l) / sizeof *(l)) - 1) 39 | 40 | #define luaL_newlib(L, l) \ 41 | (luaL_newlibtable((L), (l)), luaL_setfuncs((L), (l), 0)) 42 | #endif 43 | 44 | #ifndef MAX 45 | #define MAX(a, b) (((a) > (b))? (a) : (b)) 46 | #endif 47 | 48 | 49 | struct bench { 50 | const char *path; 51 | void *solib; 52 | size_t count; 53 | timeout_t timeout_max; 54 | int verbose; 55 | 56 | void *state; 57 | struct timeout *timeout; 58 | struct benchops ops; 59 | timeout_t curtime; 60 | }; /* struct bench */ 61 | 62 | 63 | #if __APPLE__ 64 | static mach_timebase_info_data_t timebase; 65 | #endif 66 | 67 | 68 | static int long long monotime(void) { 69 | #if __APPLE__ 70 | unsigned long long abt; 71 | 72 | abt = mach_absolute_time(); 73 | abt = abt * timebase.numer / timebase.denom; 74 | 75 | return abt / 1000LL; 76 | #else 77 | struct timespec ts; 78 | 79 | clock_gettime(CLOCK_MONOTONIC, &ts); 80 | 81 | return (ts.tv_sec * 1000000L) + (ts.tv_nsec / 1000L); 82 | #endif 83 | } /* monotime() */ 84 | 85 | 86 | static int bench_clock(lua_State *L) { 87 | lua_pushnumber(L, (double)monotime() / 1000000L); 88 | 89 | return 1; 90 | } /* bench_clock() */ 91 | 92 | 93 | static int bench_new(lua_State *L) { 94 | const char *path = luaL_checkstring(L, 1); 95 | size_t count = luaL_optinteger(L, 2, 1000000); 96 | timeout_t timeout_max = luaL_optinteger(L, 3, 300 * 1000000L); 97 | int verbose = (lua_isnone(L, 4))? 0 : lua_toboolean(L, 4); 98 | struct bench *B; 99 | struct benchops *ops; 100 | 101 | B = lua_newuserdata(L, sizeof *B); 102 | memset(B, 0, sizeof *B); 103 | 104 | luaL_getmetatable(L, "BENCH*"); 105 | lua_setmetatable(L, -2); 106 | 107 | B->count = count; 108 | B->timeout_max = timeout_max; 109 | B->verbose = verbose; 110 | 111 | if (!(B->timeout = calloc(count, sizeof *B->timeout))) 112 | return luaL_error(L, "%s", strerror(errno)); 113 | 114 | if (!(B->solib = dlopen(path, RTLD_NOW|RTLD_LOCAL))) 115 | return luaL_error(L, "%s: %s", path, dlerror()); 116 | 117 | if (!(ops = dlsym(B->solib, "benchops"))) 118 | return luaL_error(L, "%s: %s", path, dlerror()); 119 | 120 | B->ops = *ops; 121 | B->state = B->ops.init(B->timeout, B->count, B->verbose); 122 | 123 | return 1; 124 | } /* bench_new() */ 125 | 126 | 127 | static int bench_add(lua_State *L) { 128 | struct bench *B = lua_touserdata(L, 1); 129 | unsigned i; 130 | timeout_t t; 131 | 132 | i = (lua_isnoneornil(L, 2))? random() % B->count : (unsigned)luaL_checkinteger(L, 2); 133 | t = (lua_isnoneornil(L, 3))? random() % B->timeout_max : (unsigned)luaL_checkinteger(L, 3); 134 | 135 | B->ops.add(B->state, &B->timeout[i], t); 136 | 137 | return 0; 138 | } /* bench_add() */ 139 | 140 | 141 | static int bench_del(lua_State *L) { 142 | struct bench *B = lua_touserdata(L, 1); 143 | size_t i = luaL_optinteger(L, 2, random() % B->count); 144 | size_t j = luaL_optinteger(L, 3, i); 145 | 146 | while (i <= j && i < B->count) { 147 | B->ops.del(B->state, &B->timeout[i]); 148 | ++i; 149 | } 150 | 151 | return 0; 152 | } /* bench_del() */ 153 | 154 | 155 | static int bench_fill(lua_State *L) { 156 | struct bench *B = lua_touserdata(L, 1); 157 | size_t count = luaL_optinteger(L, 2, B->count); 158 | long timeout_inc = luaL_optinteger(L, 3, -1), timeout_max = 0, timeout; 159 | size_t i; 160 | 161 | if (timeout_inc < 0) { 162 | for (i = 0; i < count; i++) { 163 | timeout = random() % B->timeout_max; 164 | B->ops.add(B->state, &B->timeout[i], timeout); 165 | timeout_max = MAX(timeout, timeout_max); 166 | } 167 | } else { 168 | for (i = 0; i < count; i++) { 169 | timeout = timeout_inc + i; 170 | B->ops.add(B->state, &B->timeout[i], timeout_inc + i); 171 | timeout_max = MAX(timeout, timeout_max); 172 | } 173 | } 174 | 175 | lua_pushinteger(L, (lua_Integer)count); 176 | lua_pushinteger(L, (lua_Integer)timeout_max); 177 | 178 | return 2; 179 | } /* bench_fill() */ 180 | 181 | 182 | static int bench_expire(lua_State *L) { 183 | struct bench *B = lua_touserdata(L, 1); 184 | unsigned count = luaL_optinteger(L, 2, B->count); 185 | unsigned step = luaL_optinteger(L, 3, 300000); 186 | size_t i = 0; 187 | 188 | while (i < count && !B->ops.empty(B->state)) { 189 | B->curtime += step; 190 | B->ops.update(B->state, B->curtime); 191 | 192 | while (B->ops.get(B->state)) 193 | i++; 194 | } 195 | 196 | lua_pushinteger(L, (lua_Integer)i); 197 | 198 | return 1; 199 | } /* bench_expire() */ 200 | 201 | 202 | static int bench_empty(lua_State *L) { 203 | struct bench *B = lua_touserdata(L, 1); 204 | 205 | lua_pushboolean(L, B->ops.empty(B->state)); 206 | 207 | return 1; 208 | } /* bench_empty() */ 209 | 210 | 211 | static int bench__next(lua_State *L) { 212 | struct bench *B = lua_touserdata(L, lua_upvalueindex(1)); 213 | struct timeouts_it *it = lua_touserdata(L, lua_upvalueindex(2)); 214 | struct timeout *to; 215 | 216 | if (!B->ops.next || !(to = B->ops.next(B->state, it))) 217 | return 0; 218 | 219 | lua_pushinteger(L, luaL_optinteger(L, 2, 0) + 1); 220 | 221 | lua_newtable(L); 222 | lua_pushinteger(L, to->expires); 223 | lua_setfield(L, -2, "expires"); 224 | 225 | return 2; 226 | } /* bench__next() */ 227 | 228 | static int bench__pairs(lua_State *L) { 229 | struct timeouts_it *it; 230 | 231 | lua_settop(L, 1); 232 | 233 | it = lua_newuserdata(L, sizeof *it); 234 | TIMEOUTS_IT_INIT(it, TIMEOUTS_ALL); 235 | 236 | lua_pushcclosure(L, &bench__next, 2); 237 | lua_pushvalue(L, 1); 238 | lua_pushinteger(L, 0); 239 | 240 | return 3; 241 | } /* bench__pairs() */ 242 | 243 | 244 | static int bench__gc(lua_State *L) { 245 | struct bench *B = lua_touserdata(L, 1); 246 | 247 | if (B->state) { 248 | B->ops.destroy(B->state); 249 | B->state = NULL; 250 | } 251 | 252 | return 0; 253 | } /* bench__gc() */ 254 | 255 | 256 | static const luaL_Reg bench_methods[] = { 257 | { "add", &bench_add }, 258 | { "del", &bench_del }, 259 | { "fill", &bench_fill }, 260 | { "expire", &bench_expire }, 261 | { "empty", &bench_empty }, 262 | { "close", &bench__gc }, 263 | { NULL, NULL } 264 | }; 265 | 266 | static const luaL_Reg bench_metatable[] = { 267 | { "__pairs", &bench__pairs }, 268 | { "__gc", &bench__gc }, 269 | { NULL, NULL } 270 | }; 271 | 272 | static const luaL_Reg bench_globals[] = { 273 | { "new", &bench_new }, 274 | { "clock", &bench_clock }, 275 | { NULL, NULL } 276 | }; 277 | 278 | int luaopen_bench(lua_State *L) { 279 | #if __APPLE__ 280 | mach_timebase_info(&timebase); 281 | #endif 282 | 283 | if (luaL_newmetatable(L, "BENCH*")) { 284 | luaL_setfuncs(L, bench_metatable, 0); 285 | luaL_newlib(L, bench_methods); 286 | lua_setfield(L, -2, "__index"); 287 | } 288 | 289 | luaL_newlib(L, bench_globals); 290 | 291 | return 1; 292 | } /* luaopen_bench() */ 293 | 294 | -------------------------------------------------------------------------------- /bench/bench.h: -------------------------------------------------------------------------------- 1 | struct benchops { 2 | void *(*init)(struct timeout *, size_t, int); 3 | void (*add)(void *, struct timeout *, timeout_t); 4 | void (*del)(void *, struct timeout *); 5 | struct timeout *(*get)(void *); 6 | void (*update)(void *, timeout_t); 7 | void (*check)(void *); 8 | int (*empty)(void *); 9 | struct timeout *(*next)(void *, struct timeouts_it *); 10 | void (*destroy)(void *); 11 | }; /* struct benchops() */ 12 | -------------------------------------------------------------------------------- /bench/bench.plt: -------------------------------------------------------------------------------- 1 | set terminal postscript color 2 | 3 | set key top left 4 | set xlabel "Number of timeouts" 5 | set ylabel "Time\n(microseconds)" 6 | #set logscale x 7 | 8 | set title "Time spent installing timeouts" font ",20" 9 | plot 'heap-add.dat' using 1:($2*1000000) title "min-heap" with lines ls 1 lw 3 lc "red", \ 10 | 'wheel-add.dat' using 1:($2*1000000) title "hierarchical wheel" with lines ls 1 lw 3 lc "forest-green" 11 | 12 | set title "Time spent deleting timeouts" font ",20" 13 | plot 'heap-del.dat' using 1:($2*1000000) title "min-heap" with lines ls 1 lw 3 lc "red", \ 14 | 'wheel-del.dat' using 1:($2*1000000) title "hierarchical wheel" with lines ls 1 lw 3 lc "forest-green" 15 | 16 | set title "Time spent expiring timeouts\n(by iteratively updating clock ~1000 times)" font ",20" 17 | plot 'heap-expire.dat' using 1:($2*1000000) title "min-heap" with lines ls 1 lw 3 lc "red", \ 18 | 'wheel-expire.dat' using 1:($2*1000000) title "hierarchical wheel" with lines ls 1 lw 3 lc "forest-green" 19 | 20 | -------------------------------------------------------------------------------- /lua/Rules.mk: -------------------------------------------------------------------------------- 1 | $(LUA_APIS:%=$(top_builddir)/lua/%/timeout.so): $(top_srcdir)/lua/timeout-lua.c $(top_srcdir)/timeout.h $(top_srcdir)/timeout.c 2 | mkdir -p $(@D) 3 | @$(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) 4 | 5 | $(top_builddir)/lua/5.1/timeouts.so: $(top_builddir)/lua/5.1/timeout.so 6 | $(top_builddir)/lua/5.2/timeouts.so: $(top_builddir)/lua/5.2/timeout.so 7 | $(top_builddir)/lua/5.3/timeouts.so: $(top_builddir)/lua/5.3/timeout.so 8 | 9 | $(LUA_APIS:%=$(top_builddir)/lua/%/timeouts.so): 10 | cd $(@D) && ln -fs timeout.so timeouts.so 11 | 12 | lua-5.1: $(top_builddir)/lua/5.1/timeout.so $(top_builddir)/lua/5.1/timeouts.so 13 | lua-5.2: $(top_builddir)/lua/5.2/timeout.so $(top_builddir)/lua/5.2/timeouts.so 14 | lua-5.3: $(top_builddir)/lua/5.3/timeout.so $(top_builddir)/lua/5.3/timeouts.so 15 | 16 | lua-clean: 17 | $(RM) -r $(top_builddir)/lua/5.? 18 | 19 | clean: lua-clean 20 | 21 | -------------------------------------------------------------------------------- /lua/timeout-lua.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #if LUA_VERSION_NUM != 503 9 | #error only Lua 5.3 supported 10 | #endif 11 | 12 | #define TIMEOUT_PUBLIC static 13 | #include "timeout.h" 14 | #include "timeout.c" 15 | 16 | #define TIMEOUT_METANAME "struct timeout" 17 | #define TIMEOUTS_METANAME "struct timeouts*" 18 | 19 | static struct timeout * 20 | to_checkudata(lua_State *L, int index) 21 | { 22 | return luaL_checkudata(L, index, TIMEOUT_METANAME); 23 | } 24 | 25 | static struct timeouts * 26 | tos_checkudata(lua_State *L, int index) 27 | { 28 | return *(struct timeouts **)luaL_checkudata(L, index, TIMEOUTS_METANAME); 29 | } 30 | 31 | static void 32 | tos_bind(lua_State *L, int tos_index, int to_index) 33 | { 34 | lua_getuservalue(L, tos_index); 35 | lua_pushlightuserdata(L, to_checkudata(L, to_index)); 36 | lua_pushvalue(L, to_index); 37 | lua_rawset(L, -3); 38 | lua_pop(L, 1); 39 | } 40 | 41 | static void 42 | tos_unbind(lua_State *L, int tos_index, int to_index) 43 | { 44 | lua_getuservalue(L, tos_index); 45 | lua_pushlightuserdata(L, to_checkudata(L, to_index)); 46 | lua_pushnil(L); 47 | lua_rawset(L, -3); 48 | lua_pop(L, 1); 49 | } 50 | 51 | static int 52 | to__index(lua_State *L) 53 | { 54 | struct timeout *to = to_checkudata(L, 1); 55 | 56 | if (lua_type(L, 2 == LUA_TSTRING)) { 57 | const char *key = lua_tostring(L, 2); 58 | 59 | if (!strcmp(key, "flags")) { 60 | lua_pushinteger(L, to->flags); 61 | 62 | return 1; 63 | } else if (!strcmp(key, "expires")) { 64 | lua_pushinteger(L, to->expires); 65 | 66 | return 1; 67 | } 68 | } 69 | 70 | if (LUA_TNIL != lua_getuservalue(L, 1)) { 71 | lua_pushvalue(L, 2); 72 | if (LUA_TNIL != lua_rawget(L, -2)) 73 | return 1; 74 | } 75 | 76 | lua_pushvalue(L, 2); 77 | if (LUA_TNIL != lua_rawget(L, lua_upvalueindex(1))) 78 | return 1; 79 | 80 | return 0; 81 | } 82 | 83 | static int 84 | to__newindex(lua_State *L) 85 | { 86 | if (LUA_TNIL == lua_getuservalue(L, 1)) { 87 | lua_newtable(L); 88 | lua_pushvalue(L, -1); 89 | lua_setuservalue(L, 1); 90 | } 91 | 92 | lua_pushvalue(L, 2); 93 | lua_pushvalue(L, 3); 94 | lua_rawset(L, -3); 95 | 96 | return 0; 97 | } 98 | 99 | static int 100 | to__gc(lua_State *L) 101 | { 102 | struct timeout *to = to_checkudata(L, 1); 103 | 104 | /* 105 | * NB: On script exit it's possible for a timeout to still be 106 | * associated with a timeouts object, particularly when the timeouts 107 | * object was created first. 108 | */ 109 | timeout_del(to); 110 | 111 | return 0; 112 | } 113 | 114 | static int 115 | to_new(lua_State *L) 116 | { 117 | int flags = luaL_optinteger(L, 1, 0); 118 | struct timeout *to; 119 | 120 | to = lua_newuserdata(L, sizeof *to); 121 | timeout_init(to, flags); 122 | luaL_setmetatable(L, TIMEOUT_METANAME); 123 | 124 | return 1; 125 | } 126 | 127 | static const luaL_Reg to_methods[] = { 128 | { NULL, NULL }, 129 | }; 130 | 131 | static const luaL_Reg to_metatable[] = { 132 | { "__index", &to__index }, 133 | { "__newindex", &to__newindex }, 134 | { "__gc", &to__gc }, 135 | { NULL, NULL }, 136 | }; 137 | 138 | static const luaL_Reg to_globals[] = { 139 | { "new", &to_new }, 140 | { NULL, NULL }, 141 | }; 142 | 143 | static void 144 | to_newmetatable(lua_State *L) 145 | { 146 | if (luaL_newmetatable(L, TIMEOUT_METANAME)) { 147 | /* 148 | * fill metamethod table, capturing the methods table as an 149 | * upvalue for use by __index metamethod 150 | */ 151 | luaL_newlib(L, to_methods); 152 | luaL_setfuncs(L, to_metatable, 1); 153 | } 154 | } 155 | 156 | int 157 | luaopen_timeout(lua_State *L) 158 | { 159 | to_newmetatable(L); 160 | 161 | luaL_newlib(L, to_globals); 162 | lua_pushinteger(L, TIMEOUT_INT); 163 | lua_setfield(L, -2, "INT"); 164 | lua_pushinteger(L, TIMEOUT_ABS); 165 | lua_setfield(L, -2, "ABS"); 166 | 167 | return 1; 168 | } 169 | 170 | static int 171 | tos_update(lua_State *L) 172 | { 173 | struct timeouts *T = tos_checkudata(L, 1); 174 | lua_Number n = luaL_checknumber(L, 2); 175 | 176 | timeouts_update(T, timeouts_f2i(T, n)); 177 | 178 | lua_pushvalue(L, 1); 179 | 180 | return 1; 181 | } 182 | 183 | static int 184 | tos_step(lua_State *L) 185 | { 186 | struct timeouts *T = tos_checkudata(L, 1); 187 | lua_Number n = luaL_checknumber(L, 2); 188 | 189 | timeouts_step(T, timeouts_f2i(T, n)); 190 | 191 | lua_pushvalue(L, 1); 192 | 193 | return 1; 194 | } 195 | 196 | static int 197 | tos_timeout(lua_State *L) 198 | { 199 | struct timeouts *T = tos_checkudata(L, 1); 200 | 201 | lua_pushnumber(L, timeouts_i2f(T, timeouts_timeout(T))); 202 | 203 | return 1; 204 | } 205 | 206 | static int 207 | tos_add(lua_State *L) 208 | { 209 | struct timeouts *T = tos_checkudata(L, 1); 210 | struct timeout *to = to_checkudata(L, 2); 211 | lua_Number timeout = luaL_checknumber(L, 3); 212 | 213 | tos_bind(L, 1, 2); 214 | timeouts_addf(T, to, timeout); 215 | 216 | return lua_pushvalue(L, 1), 1; 217 | } 218 | 219 | static int 220 | tos_del(lua_State *L) 221 | { 222 | struct timeouts *T = tos_checkudata(L, 1); 223 | struct timeout *to = to_checkudata(L, 2); 224 | 225 | timeouts_del(T, to); 226 | tos_unbind(L, 1, 2); 227 | 228 | return lua_pushvalue(L, 1), 1; 229 | } 230 | 231 | static int 232 | tos_get(lua_State *L) 233 | { 234 | struct timeouts *T = tos_checkudata(L, 1); 235 | struct timeout *to; 236 | 237 | if (!(to = timeouts_get(T))) 238 | return 0; 239 | 240 | lua_getuservalue(L, 1); 241 | lua_rawgetp(L, -1, to); 242 | 243 | if (!timeout_pending(to)) 244 | tos_unbind(L, 1, lua_absindex(L, -1)); 245 | 246 | return 1; 247 | } 248 | 249 | static int 250 | tos_pending(lua_State *L) 251 | { 252 | struct timeouts *T = tos_checkudata(L, 1); 253 | 254 | lua_pushboolean(L, timeouts_pending(T)); 255 | 256 | return 1; 257 | } 258 | 259 | static int 260 | tos_expired(lua_State *L) 261 | { 262 | struct timeouts *T = tos_checkudata(L, 1); 263 | 264 | lua_pushboolean(L, timeouts_expired(T)); 265 | 266 | return 1; 267 | } 268 | 269 | static int 270 | tos_check(lua_State *L) 271 | { 272 | struct timeouts *T = tos_checkudata(L, 1); 273 | 274 | lua_pushboolean(L, timeouts_check(T, NULL)); 275 | 276 | return 1; 277 | } 278 | 279 | static int 280 | tos__next(lua_State *L) 281 | { 282 | struct timeouts *T = tos_checkudata(L, lua_upvalueindex(1)); 283 | struct timeouts_it *it = lua_touserdata(L, lua_upvalueindex(2)); 284 | struct timeout *to; 285 | 286 | if (!(to = timeouts_next(T, it))) 287 | return 0; 288 | 289 | lua_getuservalue(L, lua_upvalueindex(1)); 290 | lua_rawgetp(L, -1, to); 291 | 292 | return 1; 293 | } 294 | 295 | static int 296 | tos_timeouts(lua_State *L) 297 | { 298 | int flags = luaL_checkinteger(L, 2); 299 | struct timeouts_it *it; 300 | 301 | tos_checkudata(L, 1); 302 | lua_pushvalue(L, 1); 303 | it = lua_newuserdata(L, sizeof *it); 304 | TIMEOUTS_IT_INIT(it, flags); 305 | lua_pushcclosure(L, &tos__next, 2); 306 | 307 | return 1; 308 | } 309 | 310 | static int 311 | tos__gc(lua_State *L) 312 | { 313 | struct timeouts **tos = luaL_checkudata(L, 1, TIMEOUTS_METANAME); 314 | struct timeout *to; 315 | 316 | TIMEOUTS_FOREACH(to, *tos, TIMEOUTS_ALL) { 317 | timeouts_del(*tos, to); 318 | } 319 | 320 | timeouts_close(*tos); 321 | *tos = NULL; 322 | 323 | return 0; 324 | } 325 | 326 | static int 327 | tos_new(lua_State *L) 328 | { 329 | timeout_t hz = luaL_optinteger(L, 1, 0); 330 | struct timeouts **T; 331 | int error; 332 | 333 | T = lua_newuserdata(L, sizeof *T); 334 | luaL_setmetatable(L, TIMEOUTS_METANAME); 335 | 336 | lua_newtable(L); 337 | lua_setuservalue(L, -2); 338 | 339 | if (!(*T = timeouts_open(hz, &error))) 340 | return luaL_error(L, "%s", strerror(error)); 341 | 342 | return 1; 343 | } 344 | 345 | static const luaL_Reg tos_methods[] = { 346 | { "update", &tos_update }, 347 | { "step", &tos_step }, 348 | { "timeout", &tos_timeout }, 349 | { "add", &tos_add }, 350 | { "del", &tos_del }, 351 | { "get", &tos_get }, 352 | { "pending", &tos_pending }, 353 | { "expired", &tos_expired }, 354 | { "check", &tos_check }, 355 | { "timeouts", &tos_timeouts }, 356 | { NULL, NULL }, 357 | }; 358 | 359 | static const luaL_Reg tos_metatable[] = { 360 | { "__gc", &tos__gc }, 361 | { NULL, NULL }, 362 | }; 363 | 364 | static const luaL_Reg tos_globals[] = { 365 | { "new", &tos_new }, 366 | { NULL, NULL }, 367 | }; 368 | 369 | static void 370 | tos_newmetatable(lua_State *L) 371 | { 372 | if (luaL_newmetatable(L, TIMEOUTS_METANAME)) { 373 | luaL_setfuncs(L, tos_metatable, 0); 374 | luaL_newlib(L, tos_methods); 375 | lua_setfield(L, -2, "__index"); 376 | } 377 | } 378 | 379 | int 380 | luaopen_timeouts(lua_State *L) 381 | { 382 | to_newmetatable(L); 383 | tos_newmetatable(L); 384 | 385 | luaL_newlib(L, tos_globals); 386 | lua_pushinteger(L, TIMEOUTS_PENDING); 387 | lua_setfield(L, -2, "PENDING"); 388 | lua_pushinteger(L, TIMEOUTS_EXPIRED); 389 | lua_setfield(L, -2, "EXPIRED"); 390 | lua_pushinteger(L, TIMEOUTS_ALL); 391 | lua_setfield(L, -2, "ALL"); 392 | lua_pushinteger(L, TIMEOUTS_CLEAR); 393 | lua_setfield(L, -2, "CLEAR"); 394 | 395 | return 1; 396 | } 397 | -------------------------------------------------------------------------------- /test-timeout.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "timeout.h" 8 | 9 | #define THE_END_OF_TIME ((timeout_t)-1) 10 | 11 | static int check_misc(void) { 12 | if (TIMEOUT_VERSION != timeout_version()) 13 | return 1; 14 | if (TIMEOUT_V_REL != timeout_v_rel()) 15 | return 1; 16 | if (TIMEOUT_V_API != timeout_v_api()) 17 | return 1; 18 | if (TIMEOUT_V_ABI != timeout_v_abi()) 19 | return 1; 20 | if (strcmp(timeout_vendor(), TIMEOUT_VENDOR)) 21 | return 1; 22 | return 0; 23 | } 24 | 25 | static int check_open_close(timeout_t hz_set, timeout_t hz_expect) { 26 | int err=0; 27 | struct timeouts *tos = timeouts_open(hz_set, &err); 28 | if (!tos) 29 | return 1; 30 | if (err) 31 | return 1; 32 | if (hz_expect != timeouts_hz(tos)) 33 | return 1; 34 | timeouts_close(tos); 35 | return 0; 36 | } 37 | 38 | /* Not very random */ 39 | static timeout_t random_to(timeout_t min, timeout_t max) 40 | { 41 | if (max <= min) 42 | return min; 43 | /* Not actually all that random, but should exercise the code. */ 44 | timeout_t rand64 = random() * (timeout_t)INT_MAX + random(); 45 | return min + (rand64 % (max-min)); 46 | } 47 | 48 | /* configuration for check_randomized */ 49 | struct rand_cfg { 50 | /* When creating timeouts, smallest possible delay */ 51 | timeout_t min_timeout; 52 | /* When creating timeouts, largest possible delay */ 53 | timeout_t max_timeout; 54 | /* First time to start the clock at. */ 55 | timeout_t start_at; 56 | /* Do not advance the clock past this time. */ 57 | timeout_t end_at; 58 | /* Number of timeouts to create and monitor. */ 59 | int n_timeouts; 60 | /* Advance the clock by no more than this each step. */ 61 | timeout_t max_step; 62 | /* Use relative timers and stepping */ 63 | int relative; 64 | /* Every time the clock ticks, try removing this many timeouts at 65 | * random. */ 66 | int try_removing; 67 | /* When we're done, advance the clock to the end of time. */ 68 | int finalize; 69 | }; 70 | 71 | static int check_randomized(const struct rand_cfg *cfg) 72 | { 73 | #define FAIL() do { \ 74 | printf("Failure on line %d\n", __LINE__); \ 75 | goto done; \ 76 | } while (0) 77 | 78 | int i, err; 79 | int rv = 1; 80 | struct timeout *t = calloc(cfg->n_timeouts, sizeof(struct timeout)); 81 | timeout_t *timeouts = calloc(cfg->n_timeouts, sizeof(timeout_t)); 82 | uint8_t *fired = calloc(cfg->n_timeouts, sizeof(uint8_t)); 83 | uint8_t *found = calloc(cfg->n_timeouts, sizeof(uint8_t)); 84 | uint8_t *deleted = calloc(cfg->n_timeouts, sizeof(uint8_t)); 85 | struct timeouts *tos = timeouts_open(0, &err); 86 | timeout_t now = cfg->start_at; 87 | int n_added_pending = 0, cnt_added_pending = 0; 88 | int n_added_expired = 0, cnt_added_expired = 0; 89 | struct timeouts_it it_p, it_e, it_all; 90 | int p_done = 0, e_done = 0, all_done = 0; 91 | struct timeout *to = NULL; 92 | const int rel = cfg->relative; 93 | 94 | if (!t || !timeouts || !tos || !fired || !found || !deleted) 95 | FAIL(); 96 | timeouts_update(tos, cfg->start_at); 97 | 98 | for (i = 0; i < cfg->n_timeouts; ++i) { 99 | if (&t[i] != timeout_init(&t[i], rel ? 0 : TIMEOUT_ABS)) 100 | FAIL(); 101 | if (timeout_pending(&t[i])) 102 | FAIL(); 103 | if (timeout_expired(&t[i])) 104 | FAIL(); 105 | 106 | timeouts[i] = random_to(cfg->min_timeout, cfg->max_timeout); 107 | 108 | timeouts_add(tos, &t[i], timeouts[i] - (rel ? now : 0)); 109 | if (timeouts[i] <= cfg->start_at) { 110 | if (timeout_pending(&t[i])) 111 | FAIL(); 112 | if (! timeout_expired(&t[i])) 113 | FAIL(); 114 | ++n_added_expired; 115 | } else { 116 | if (! timeout_pending(&t[i])) 117 | FAIL(); 118 | if (timeout_expired(&t[i])) 119 | FAIL(); 120 | ++n_added_pending; 121 | } 122 | } 123 | 124 | if (!!n_added_pending != timeouts_pending(tos)) 125 | FAIL(); 126 | if (!!n_added_expired != timeouts_expired(tos)) 127 | FAIL(); 128 | 129 | /* Test foreach, interleaving a few iterators. */ 130 | TIMEOUTS_IT_INIT(&it_p, TIMEOUTS_PENDING); 131 | TIMEOUTS_IT_INIT(&it_e, TIMEOUTS_EXPIRED); 132 | TIMEOUTS_IT_INIT(&it_all, TIMEOUTS_ALL); 133 | while (! (p_done && e_done && all_done)) { 134 | if (!p_done) { 135 | to = timeouts_next(tos, &it_p); 136 | if (to) { 137 | i = to - &t[0]; 138 | ++found[i]; 139 | ++cnt_added_pending; 140 | } else { 141 | p_done = 1; 142 | } 143 | } 144 | if (!e_done) { 145 | to = timeouts_next(tos, &it_e); 146 | if (to) { 147 | i = to - &t[0]; 148 | ++found[i]; 149 | ++cnt_added_expired; 150 | } else { 151 | e_done = 1; 152 | } 153 | } 154 | if (!all_done) { 155 | to = timeouts_next(tos, &it_all); 156 | if (to) { 157 | i = to - &t[0]; 158 | ++found[i]; 159 | } else { 160 | all_done = 1; 161 | } 162 | } 163 | } 164 | 165 | for (i = 0; i < cfg->n_timeouts; ++i) { 166 | if (found[i] != 2) 167 | FAIL(); 168 | } 169 | if (cnt_added_expired != n_added_expired) 170 | FAIL(); 171 | if (cnt_added_pending != n_added_pending) 172 | FAIL(); 173 | 174 | while (NULL != (to = timeouts_get(tos))) { 175 | i = to - &t[0]; 176 | assert(&t[i] == to); 177 | if (timeouts[i] > cfg->start_at) 178 | FAIL(); /* shouldn't have happened yet */ 179 | 180 | --n_added_expired; /* drop expired timeouts. */ 181 | ++fired[i]; 182 | } 183 | 184 | if (n_added_expired != 0) 185 | FAIL(); 186 | 187 | while (now < cfg->end_at) { 188 | int n_fired_this_time = 0; 189 | timeout_t first_at = timeouts_timeout(tos) + now; 190 | 191 | timeout_t oldtime = now; 192 | timeout_t step = random_to(1, cfg->max_step); 193 | int another; 194 | now += step; 195 | if (rel) 196 | timeouts_step(tos, step); 197 | else 198 | timeouts_update(tos, now); 199 | 200 | for (i = 0; i < cfg->try_removing; ++i) { 201 | int idx = random() % cfg->n_timeouts; 202 | if (! fired[idx]) { 203 | timeout_del(&t[idx]); 204 | ++deleted[idx]; 205 | } 206 | } 207 | 208 | another = (timeouts_timeout(tos) == 0); 209 | 210 | while (NULL != (to = timeouts_get(tos))) { 211 | if (! another) 212 | FAIL(); /* Thought we saw the last one! */ 213 | i = to - &t[0]; 214 | assert(&t[i] == to); 215 | if (timeouts[i] > now) 216 | FAIL(); /* shouldn't have happened yet */ 217 | if (timeouts[i] <= oldtime) 218 | FAIL(); /* should have happened already */ 219 | if (timeouts[i] < first_at) 220 | FAIL(); /* first_at should've been earlier */ 221 | fired[i]++; 222 | n_fired_this_time++; 223 | another = (timeouts_timeout(tos) == 0); 224 | } 225 | if (n_fired_this_time && first_at > now) 226 | FAIL(); /* first_at should've been earlier */ 227 | if (another) 228 | FAIL(); /* Huh? We think there are more? */ 229 | if (!timeouts_check(tos, stderr)) 230 | FAIL(); 231 | } 232 | 233 | for (i = 0; i < cfg->n_timeouts; ++i) { 234 | if (fired[i] > 1) 235 | FAIL(); /* Nothing fired twice. */ 236 | if (timeouts[i] <= now) { 237 | if (!(fired[i] || deleted[i])) 238 | FAIL(); 239 | } else { 240 | if (fired[i]) 241 | FAIL(); 242 | } 243 | if (fired[i] && deleted[i]) 244 | FAIL(); 245 | if (cfg->finalize > 1) { 246 | if (!fired[i]) 247 | timeout_del(&t[i]); 248 | } 249 | } 250 | 251 | /* Now nothing more should fire between now and the end of time. */ 252 | if (cfg->finalize) { 253 | timeouts_update(tos, THE_END_OF_TIME); 254 | if (cfg->finalize > 1) { 255 | if (timeouts_get(tos)) 256 | FAIL(); 257 | TIMEOUTS_FOREACH(to, tos, TIMEOUTS_ALL) 258 | FAIL(); 259 | } 260 | } 261 | rv = 0; 262 | 263 | done: 264 | if (tos) timeouts_close(tos); 265 | if (t) free(t); 266 | if (timeouts) free(timeouts); 267 | if (fired) free(fired); 268 | if (found) free(found); 269 | if (deleted) free(deleted); 270 | return rv; 271 | } 272 | 273 | struct intervals_cfg { 274 | const timeout_t *timeouts; 275 | int n_timeouts; 276 | timeout_t start_at; 277 | timeout_t end_at; 278 | timeout_t skip; 279 | }; 280 | 281 | int 282 | check_intervals(struct intervals_cfg *cfg) 283 | { 284 | int i, err; 285 | int rv = 1; 286 | struct timeout *to; 287 | struct timeout *t = calloc(cfg->n_timeouts, sizeof(struct timeout)); 288 | unsigned *fired = calloc(cfg->n_timeouts, sizeof(unsigned)); 289 | struct timeouts *tos = timeouts_open(0, &err); 290 | 291 | timeout_t now = cfg->start_at; 292 | if (!t || !tos || !fired) 293 | FAIL(); 294 | 295 | timeouts_update(tos, now); 296 | 297 | for (i = 0; i < cfg->n_timeouts; ++i) { 298 | if (&t[i] != timeout_init(&t[i], TIMEOUT_INT)) 299 | FAIL(); 300 | if (timeout_pending(&t[i])) 301 | FAIL(); 302 | if (timeout_expired(&t[i])) 303 | FAIL(); 304 | 305 | timeouts_add(tos, &t[i], cfg->timeouts[i]); 306 | if (! timeout_pending(&t[i])) 307 | FAIL(); 308 | if (timeout_expired(&t[i])) 309 | FAIL(); 310 | } 311 | 312 | while (now < cfg->end_at) { 313 | timeout_t delay = timeouts_timeout(tos); 314 | if (cfg->skip && delay < cfg->skip) 315 | delay = cfg->skip; 316 | timeouts_step(tos, delay); 317 | now += delay; 318 | 319 | while (NULL != (to = timeouts_get(tos))) { 320 | i = to - &t[0]; 321 | assert(&t[i] == to); 322 | fired[i]++; 323 | if (0 != (to->expires - cfg->start_at) % cfg->timeouts[i]) 324 | FAIL(); 325 | if (to->expires <= now) 326 | FAIL(); 327 | if (to->expires > now + cfg->timeouts[i]) 328 | FAIL(); 329 | } 330 | if (!timeouts_check(tos, stderr)) 331 | FAIL(); 332 | } 333 | 334 | timeout_t duration = now - cfg->start_at; 335 | for (i = 0; i < cfg->n_timeouts; ++i) { 336 | if (cfg->skip) { 337 | if (fired[i] > duration / cfg->timeouts[i]) 338 | FAIL(); 339 | } else { 340 | if (fired[i] != duration / cfg->timeouts[i]) 341 | FAIL(); 342 | } 343 | if (!timeout_pending(&t[i])) 344 | FAIL(); 345 | } 346 | 347 | rv = 0; 348 | done: 349 | if (t) free(t); 350 | if (fired) free(fired); 351 | if (tos) free(tos); 352 | return rv; 353 | } 354 | 355 | int 356 | main(int argc, char **argv) 357 | { 358 | int j; 359 | int n_failed = 0; 360 | #define DO(fn) do { \ 361 | printf("."); fflush(stdout); \ 362 | if (fn) { \ 363 | ++n_failed; \ 364 | printf("%s failed\n", #fn); \ 365 | } \ 366 | } while (0) 367 | 368 | #define DO_N(n, fn) do { \ 369 | for (j = 0; j < (n); ++j) { \ 370 | DO(fn); \ 371 | } \ 372 | } while (0) 373 | 374 | DO(check_misc()); 375 | DO(check_open_close(1000, 1000)); 376 | DO(check_open_close(0, TIMEOUT_mHZ)); 377 | 378 | struct rand_cfg cfg1 = { 379 | .min_timeout = 1, 380 | .max_timeout = 100, 381 | .start_at = 5, 382 | .end_at = 1000, 383 | .n_timeouts = 1000, 384 | .max_step = 10, 385 | .relative = 0, 386 | .try_removing = 0, 387 | .finalize = 2, 388 | }; 389 | DO_N(300,check_randomized(&cfg1)); 390 | 391 | struct rand_cfg cfg2 = { 392 | .min_timeout = 20, 393 | .max_timeout = 1000, 394 | .start_at = 10, 395 | .end_at = 100, 396 | .n_timeouts = 1000, 397 | .max_step = 5, 398 | .relative = 1, 399 | .try_removing = 0, 400 | .finalize = 2, 401 | }; 402 | DO_N(300,check_randomized(&cfg2)); 403 | 404 | struct rand_cfg cfg2b = { 405 | .min_timeout = 20, 406 | .max_timeout = 1000, 407 | .start_at = 10, 408 | .end_at = 100, 409 | .n_timeouts = 1000, 410 | .max_step = 5, 411 | .relative = 1, 412 | .try_removing = 0, 413 | .finalize = 1, 414 | }; 415 | DO_N(300,check_randomized(&cfg2b)); 416 | 417 | struct rand_cfg cfg2c = { 418 | .min_timeout = 20, 419 | .max_timeout = 1000, 420 | .start_at = 10, 421 | .end_at = 100, 422 | .n_timeouts = 1000, 423 | .max_step = 5, 424 | .relative = 1, 425 | .try_removing = 0, 426 | .finalize = 0, 427 | }; 428 | DO_N(300,check_randomized(&cfg2c)); 429 | 430 | struct rand_cfg cfg3 = { 431 | .min_timeout = 2000, 432 | .max_timeout = ((uint64_t)1) << 50, 433 | .start_at = 100, 434 | .end_at = ((uint64_t)1) << 49, 435 | .n_timeouts = 1000, 436 | .max_step = ((uint64_t)1) << 31, 437 | .relative = 0, 438 | .try_removing = 0, 439 | .finalize = 2, 440 | }; 441 | DO_N(10,check_randomized(&cfg3)); 442 | 443 | struct rand_cfg cfg3b = { 444 | .min_timeout = ((uint64_t)1) << 50, 445 | .max_timeout = ((uint64_t)1) << 52, 446 | .start_at = 100, 447 | .end_at = ((uint64_t)1) << 53, 448 | .n_timeouts = 1000, 449 | .max_step = ((uint64_t)1)<<48, 450 | .relative = 0, 451 | .try_removing = 0, 452 | .finalize = 2, 453 | }; 454 | DO_N(10,check_randomized(&cfg3b)); 455 | 456 | struct rand_cfg cfg4 = { 457 | .min_timeout = 2000, 458 | .max_timeout = ((uint64_t)1) << 30, 459 | .start_at = 100, 460 | .end_at = ((uint64_t)1) << 26, 461 | .n_timeouts = 10000, 462 | .max_step = 1<<16, 463 | .relative = 0, 464 | .try_removing = 3, 465 | .finalize = 2, 466 | }; 467 | DO_N(10,check_randomized(&cfg4)); 468 | 469 | const timeout_t primes[] = { 470 | 2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53, 471 | 59,61,67,71,73,79,83,89,97 472 | }; 473 | const timeout_t factors_of_1337[] = { 474 | 1, 7, 191, 1337 475 | }; 476 | const timeout_t multiples_of_five[] = { 477 | 5, 10, 15, 20, 25, 30, 35, 40, 45, 50 478 | }; 479 | 480 | struct intervals_cfg icfg1 = { 481 | .timeouts = primes, 482 | .n_timeouts = sizeof(primes)/sizeof(timeout_t), 483 | .start_at = 50, 484 | .end_at = 5322, 485 | .skip = 0, 486 | }; 487 | DO(check_intervals(&icfg1)); 488 | 489 | struct intervals_cfg icfg2 = { 490 | .timeouts = factors_of_1337, 491 | .n_timeouts = sizeof(factors_of_1337)/sizeof(timeout_t), 492 | .start_at = 50, 493 | .end_at = 50000, 494 | .skip = 0, 495 | }; 496 | DO(check_intervals(&icfg2)); 497 | 498 | struct intervals_cfg icfg3 = { 499 | .timeouts = multiples_of_five, 500 | .n_timeouts = sizeof(multiples_of_five)/sizeof(timeout_t), 501 | .start_at = 49, 502 | .end_at = 5333, 503 | .skip = 0, 504 | }; 505 | DO(check_intervals(&icfg3)); 506 | 507 | struct intervals_cfg icfg4 = { 508 | .timeouts = primes, 509 | .n_timeouts = sizeof(primes)/sizeof(timeout_t), 510 | .start_at = 50, 511 | .end_at = 5322, 512 | .skip = 16, 513 | }; 514 | DO(check_intervals(&icfg4)); 515 | 516 | if (n_failed) { 517 | puts("\nFAIL"); 518 | } else { 519 | puts("\nOK"); 520 | } 521 | return !!n_failed; 522 | } 523 | 524 | /* TODO: 525 | 526 | * Solve PR#3. 527 | 528 | * Investigate whether any untaken branches are possible. 529 | 530 | */ 531 | -------------------------------------------------------------------------------- /timeout-bitops.c: -------------------------------------------------------------------------------- 1 | #include 2 | #ifdef _MSC_VER 3 | #include /* _BitScanForward, _BitScanReverse */ 4 | #endif 5 | 6 | /* First define ctz and clz functions; these are compiler-dependent if 7 | * you want them to be fast. */ 8 | #if defined(__GNUC__) && !defined(TIMEOUT_DISABLE_GNUC_BITOPS) 9 | 10 | /* On GCC and clang and some others, we can use __builtin functions. They 11 | * are not defined for n==0, but timeout.s never calls them with n==0. */ 12 | 13 | #define ctz64(n) __builtin_ctzll(n) 14 | #define clz64(n) __builtin_clzll(n) 15 | #if LONG_BITS == 32 16 | #define ctz32(n) __builtin_ctzl(n) 17 | #define clz32(n) __builtin_clzl(n) 18 | #else 19 | #define ctz32(n) __builtin_ctz(n) 20 | #define clz32(n) __builtin_clz(n) 21 | #endif 22 | 23 | #elif defined(_MSC_VER) && !defined(TIMEOUT_DISABLE_MSVC_BITOPS) 24 | 25 | /* On MSVC, we have these handy functions. We can ignore their return 26 | * values, since we will never supply val == 0. */ 27 | 28 | static __inline int ctz32(unsigned long val) 29 | { 30 | DWORD zeros = 0; 31 | _BitScanForward(&zeros, val); 32 | return zeros; 33 | } 34 | static __inline int clz32(unsigned long val) 35 | { 36 | DWORD zeros = 0; 37 | _BitScanReverse(&zeros, val); 38 | return zeros; 39 | } 40 | #ifdef _WIN64 41 | /* According to the documentation, these only exist on Win64. */ 42 | static __inline int ctz64(uint64_t val) 43 | { 44 | DWORD zeros = 0; 45 | _BitScanForward64(&zeros, val); 46 | return zeros; 47 | } 48 | static __inline int clz64(uint64_t val) 49 | { 50 | DWORD zeros = 0; 51 | _BitScanReverse64(&zeros, val); 52 | return zeros; 53 | } 54 | #else 55 | static __inline int ctz64(uint64_t val) 56 | { 57 | uint32_t lo = (uint32_t) val; 58 | uint32_t hi = (uint32_t) (val >> 32); 59 | return lo ? ctz32(lo) : 32 + ctz32(hi); 60 | } 61 | static __inline int clz64(uint64_t val) 62 | { 63 | uint32_t lo = (uint32_t) val; 64 | uint32_t hi = (uint32_t) (val >> 32); 65 | return hi ? clz32(hi) : 32 + clz32(lo); 66 | } 67 | #endif 68 | 69 | /* End of MSVC case. */ 70 | 71 | #else 72 | 73 | /* TODO: There are more clever ways to do this in the generic case. */ 74 | 75 | 76 | #define process_(one, cz_bits, bits) \ 77 | if (x < ( one << (cz_bits - bits))) { rv += bits; x <<= bits; } 78 | 79 | #define process64(bits) process_((UINT64_C(1)), 64, (bits)) 80 | static inline int clz64(uint64_t x) 81 | { 82 | int rv = 0; 83 | 84 | process64(32); 85 | process64(16); 86 | process64(8); 87 | process64(4); 88 | process64(2); 89 | process64(1); 90 | return rv; 91 | } 92 | #define process32(bits) process_((UINT32_C(1)), 32, (bits)) 93 | static inline int clz32(uint32_t x) 94 | { 95 | int rv = 0; 96 | 97 | process32(16); 98 | process32(8); 99 | process32(4); 100 | process32(2); 101 | process32(1); 102 | return rv; 103 | } 104 | 105 | #undef process_ 106 | #undef process32 107 | #undef process64 108 | #define process_(one, bits) \ 109 | if ((x & ((one << (bits))-1)) == 0) { rv += bits; x >>= bits; } 110 | 111 | #define process64(bits) process_((UINT64_C(1)), bits) 112 | static inline int ctz64(uint64_t x) 113 | { 114 | int rv = 0; 115 | 116 | process64(32); 117 | process64(16); 118 | process64(8); 119 | process64(4); 120 | process64(2); 121 | process64(1); 122 | return rv; 123 | } 124 | 125 | #define process32(bits) process_((UINT32_C(1)), bits) 126 | static inline int ctz32(uint32_t x) 127 | { 128 | int rv = 0; 129 | 130 | process32(16); 131 | process32(8); 132 | process32(4); 133 | process32(2); 134 | process32(1); 135 | return rv; 136 | } 137 | 138 | #undef process32 139 | #undef process64 140 | #undef process_ 141 | 142 | /* End of generic case */ 143 | 144 | #endif /* End of defining ctz */ 145 | 146 | #ifdef TEST_BITOPS 147 | #include 148 | #include 149 | 150 | static uint64_t testcases[] = { 151 | 13371337 * 10, 152 | 100, 153 | 385789752, 154 | 82574, 155 | (((uint64_t)1)<<63) + (((uint64_t)1)<<31) + 10101 156 | }; 157 | 158 | static int 159 | naive_clz(int bits, uint64_t v) 160 | { 161 | int r = 0; 162 | uint64_t bit = ((uint64_t)1) << (bits-1); 163 | while (bit && 0 == (v & bit)) { 164 | r++; 165 | bit >>= 1; 166 | } 167 | /* printf("clz(%d,%lx) -> %d\n", bits, v, r); */ 168 | return r; 169 | } 170 | 171 | static int 172 | naive_ctz(int bits, uint64_t v) 173 | { 174 | int r = 0; 175 | uint64_t bit = 1; 176 | while (bit && 0 == (v & bit)) { 177 | r++; 178 | bit <<= 1; 179 | if (r == bits) 180 | break; 181 | } 182 | /* printf("ctz(%d,%lx) -> %d\n", bits, v, r); */ 183 | return r; 184 | } 185 | 186 | static int 187 | check(uint64_t vv) 188 | { 189 | uint32_t v32 = (uint32_t) vv; 190 | 191 | if (vv == 0) 192 | return 1; /* c[tl]z64(0) is undefined. */ 193 | 194 | if (ctz64(vv) != naive_ctz(64, vv)) { 195 | printf("mismatch with ctz64: %d\n", ctz64(vv)); 196 | exit(1); 197 | return 0; 198 | } 199 | if (clz64(vv) != naive_clz(64, vv)) { 200 | printf("mismatch with clz64: %d\n", clz64(vv)); 201 | exit(1); 202 | return 0; 203 | } 204 | 205 | if (v32 == 0) 206 | return 1; /* c[lt]z(0) is undefined. */ 207 | 208 | if (ctz32(v32) != naive_ctz(32, v32)) { 209 | printf("mismatch with ctz32: %d\n", ctz32(v32)); 210 | exit(1); 211 | return 0; 212 | } 213 | if (clz32(v32) != naive_clz(32, v32)) { 214 | printf("mismatch with clz32: %d\n", clz32(v32)); 215 | exit(1); 216 | return 0; 217 | } 218 | return 1; 219 | } 220 | 221 | int 222 | main(int c, char **v) 223 | { 224 | unsigned int i; 225 | const unsigned int n = sizeof(testcases)/sizeof(testcases[0]); 226 | int result = 0; 227 | 228 | for (i = 0; i <= 63; ++i) { 229 | uint64_t x = 1 << i; 230 | if (!check(x)) 231 | result = 1; 232 | --x; 233 | if (!check(x)) 234 | result = 1; 235 | } 236 | 237 | for (i = 0; i < n; ++i) { 238 | if (! check(testcases[i])) 239 | result = 1; 240 | } 241 | if (result) { 242 | puts("FAIL"); 243 | } else { 244 | puts("OK"); 245 | } 246 | return result; 247 | } 248 | #endif 249 | 250 | -------------------------------------------------------------------------------- /timeout-debug.h: -------------------------------------------------------------------------------- 1 | /* 2 | * D E B U G R O U T I N E S 3 | * 4 | * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 5 | 6 | #if TIMEOUT_DEBUG - 0 7 | #include 8 | #include 9 | 10 | #undef TIMEOUT_DEBUG 11 | #define TIMEOUT_DEBUG 1 12 | #define DEBUG_LEVEL timeout_debug 13 | 14 | static int timeout_debug; 15 | 16 | #define SAYit_(lvl, fmt, ...) do { \ 17 | if (DEBUG_LEVEL >= (lvl)) \ 18 | fprintf(stderr, fmt "%s", __FILE__, __LINE__, __func__, __VA_ARGS__); \ 19 | } while (0) 20 | 21 | #define SAYit(lvl, ...) SAYit_((lvl), "%s:%d:%s: " __VA_ARGS__, "\n") 22 | 23 | #define PANIC(...) do { \ 24 | SAYit(0, __VA_ARGS__); \ 25 | _Exit(EXIT_FAILURE); \ 26 | } while (0) 27 | #else 28 | #undef TIMEOUT_DEBUG 29 | #define TIMEOUT_DEBUG 0 30 | #define DEBUG_LEVEL 0 31 | 32 | #define SAYit(...) (void)0 33 | #endif 34 | 35 | #define SAY(...) SAYit(1, __VA_ARGS__) 36 | #define HAI SAY("HAI") 37 | 38 | 39 | static inline char *fmt_(char *buf, uint64_t ts, int wheel_bit, int wheel_num) { 40 | char *p = buf; 41 | int wheel, n, i; 42 | 43 | for (wheel = wheel_num - 2; wheel >= 0; wheel--) { 44 | n = ((1 << wheel_bit) - 1) & (ts >> (wheel * WHEEL_BIT)); 45 | 46 | for (i = wheel_bit - 1; i >= 0; i--) { 47 | *p++ = '0' + !!(n & (1 << i)); 48 | } 49 | 50 | if (wheel != 0) 51 | *p++ = ':'; 52 | } 53 | 54 | *p = 0; 55 | 56 | return buf; 57 | } /* fmt_() */ 58 | 59 | #define fmt(ts) fmt_(((char[((1 << WHEEL_BIT) * WHEEL_NUM) + WHEEL_NUM + 1]){ 0 }), (ts), WHEEL_BIT, WHEEL_NUM) 60 | 61 | 62 | static inline char *bin64_(char *buf, uint64_t n, int wheel_bit) { 63 | char *p = buf; 64 | int i; 65 | 66 | for (i = 0; i < (1 << wheel_bit); i++) { 67 | *p++ = "01"[0x1 & (n >> (((1 << wheel_bit) - 1) - i))]; 68 | } 69 | 70 | *p = 0; 71 | 72 | return buf; 73 | } /* bin64_() */ 74 | 75 | #define bin64(ts) bin64_(((char[((1 << WHEEL_BIT) * WHEEL_NUM) + 1]){ 0 }), (ts), WHEEL_BIT) 76 | 77 | 78 | -------------------------------------------------------------------------------- /timeout.c: -------------------------------------------------------------------------------- 1 | /* ========================================================================== 2 | * timeout.c - Tickless hierarchical timing wheel. 3 | * -------------------------------------------------------------------------- 4 | * Copyright (c) 2013, 2014 William Ahern 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a 7 | * copy of this software and associated documentation files (the 8 | * "Software"), to deal in the Software without restriction, including 9 | * without limitation the rights to use, copy, modify, merge, publish, 10 | * distribute, sublicense, and/or sell copies of the Software, and to permit 11 | * persons to whom the Software is furnished to do so, subject to the 12 | * following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included 15 | * in all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 18 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 20 | * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 21 | * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 22 | * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 23 | * USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | * ========================================================================== 25 | */ 26 | #include /* CHAR_BIT */ 27 | 28 | #include /* NULL */ 29 | #include /* malloc(3) free(3) */ 30 | #include /* FILE fprintf(3) */ 31 | 32 | #include /* UINT64_C uint64_t */ 33 | 34 | #include /* memset(3) */ 35 | 36 | #include /* errno */ 37 | 38 | #include /* TAILQ(3) */ 39 | 40 | #include "timeout.h" 41 | 42 | #if TIMEOUT_DEBUG - 0 43 | #include "timeout-debug.h" 44 | #endif 45 | 46 | #ifdef TIMEOUT_DISABLE_RELATIVE_ACCESS 47 | #define TO_SET_TIMEOUTS(to, T) ((void)0) 48 | #else 49 | #define TO_SET_TIMEOUTS(to, T) ((to)->timeouts = (T)) 50 | #endif 51 | 52 | /* 53 | * A N C I L L A R Y R O U T I N E S 54 | * 55 | * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 56 | 57 | #define abstime_t timeout_t /* for documentation purposes */ 58 | #define reltime_t timeout_t /* "" */ 59 | 60 | #if !defined countof 61 | #define countof(a) (sizeof (a) / sizeof *(a)) 62 | #endif 63 | 64 | #if !defined endof 65 | #define endof(a) (&(a)[countof(a)]) 66 | #endif 67 | 68 | #if !defined MIN 69 | #define MIN(a, b) (((a) < (b))? (a) : (b)) 70 | #endif 71 | 72 | #if !defined MAX 73 | #define MAX(a, b) (((a) > (b))? (a) : (b)) 74 | #endif 75 | 76 | #if !defined TAILQ_CONCAT 77 | #define TAILQ_CONCAT(head1, head2, field) do { \ 78 | if (!TAILQ_EMPTY(head2)) { \ 79 | *(head1)->tqh_last = (head2)->tqh_first; \ 80 | (head2)->tqh_first->field.tqe_prev = (head1)->tqh_last; \ 81 | (head1)->tqh_last = (head2)->tqh_last; \ 82 | TAILQ_INIT((head2)); \ 83 | } \ 84 | } while (0) 85 | #endif 86 | 87 | #if !defined TAILQ_FOREACH_SAFE 88 | #define TAILQ_FOREACH_SAFE(var, head, field, tvar) \ 89 | for ((var) = TAILQ_FIRST(head); \ 90 | (var) && ((tvar) = TAILQ_NEXT(var, field), 1); \ 91 | (var) = (tvar)) 92 | #endif 93 | 94 | 95 | /* 96 | * B I T M A N I P U L A T I O N R O U T I N E S 97 | * 98 | * The macros and routines below implement wheel parameterization. The 99 | * inputs are: 100 | * 101 | * WHEEL_BIT - The number of value bits mapped in each wheel. The 102 | * lowest-order WHEEL_BIT bits index the lowest-order (highest 103 | * resolution) wheel, the next group of WHEEL_BIT bits the 104 | * higher wheel, etc. 105 | * 106 | * WHEEL_NUM - The number of wheels. WHEEL_BIT * WHEEL_NUM = the number of 107 | * value bits used by all the wheels. For the default of 6 and 108 | * 4, only the low 24 bits are processed. Any timeout value 109 | * larger than this will cycle through again. 110 | * 111 | * The implementation uses bit fields to remember which slot in each wheel 112 | * is populated, and to generate masks of expiring slots according to the 113 | * current update interval (i.e. the "tickless" aspect). The slots to 114 | * process in a wheel are (populated-set & interval-mask). 115 | * 116 | * WHEEL_BIT cannot be larger than 6 bits because 2^6 -> 64 is the largest 117 | * number of slots which can be tracked in a uint64_t integer bit field. 118 | * WHEEL_BIT cannot be smaller than 3 bits because of our rotr and rotl 119 | * routines, which only operate on all the value bits in an integer, and 120 | * there's no integer smaller than uint8_t. 121 | * 122 | * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 123 | 124 | #if !defined WHEEL_BIT 125 | #define WHEEL_BIT 6 126 | #endif 127 | 128 | #if !defined WHEEL_NUM 129 | #define WHEEL_NUM 4 130 | #endif 131 | 132 | #define WHEEL_LEN (1U << WHEEL_BIT) 133 | #define WHEEL_MAX (WHEEL_LEN - 1) 134 | #define WHEEL_MASK (WHEEL_LEN - 1) 135 | #define TIMEOUT_MAX ((TIMEOUT_C(1) << (WHEEL_BIT * WHEEL_NUM)) - 1) 136 | 137 | #include "timeout-bitops.c" 138 | 139 | #if WHEEL_BIT == 6 140 | #define ctz(n) ctz64(n) 141 | #define clz(n) clz64(n) 142 | #define fls(n) ((int)(64 - clz64(n))) 143 | #else 144 | #define ctz(n) ctz32(n) 145 | #define clz(n) clz32(n) 146 | #define fls(n) ((int)(32 - clz32(n))) 147 | #endif 148 | 149 | #if WHEEL_BIT == 6 150 | #define WHEEL_C(n) UINT64_C(n) 151 | #define WHEEL_PRIu PRIu64 152 | #define WHEEL_PRIx PRIx64 153 | 154 | typedef uint64_t wheel_t; 155 | 156 | #elif WHEEL_BIT == 5 157 | 158 | #define WHEEL_C(n) UINT32_C(n) 159 | #define WHEEL_PRIu PRIu32 160 | #define WHEEL_PRIx PRIx32 161 | 162 | typedef uint32_t wheel_t; 163 | 164 | #elif WHEEL_BIT == 4 165 | 166 | #define WHEEL_C(n) UINT16_C(n) 167 | #define WHEEL_PRIu PRIu16 168 | #define WHEEL_PRIx PRIx16 169 | 170 | typedef uint16_t wheel_t; 171 | 172 | #elif WHEEL_BIT == 3 173 | 174 | #define WHEEL_C(n) UINT8_C(n) 175 | #define WHEEL_PRIu PRIu8 176 | #define WHEEL_PRIx PRIx8 177 | 178 | typedef uint8_t wheel_t; 179 | 180 | #else 181 | #error invalid WHEEL_BIT value 182 | #endif 183 | 184 | 185 | static inline wheel_t rotl(const wheel_t v, int c) { 186 | if (!(c &= (sizeof v * CHAR_BIT - 1))) 187 | return v; 188 | 189 | return (v << c) | (v >> (sizeof v * CHAR_BIT - c)); 190 | } /* rotl() */ 191 | 192 | 193 | static inline wheel_t rotr(const wheel_t v, int c) { 194 | if (!(c &= (sizeof v * CHAR_BIT - 1))) 195 | return v; 196 | 197 | return (v >> c) | (v << (sizeof v * CHAR_BIT - c)); 198 | } /* rotr() */ 199 | 200 | 201 | /* 202 | * T I M E R R O U T I N E S 203 | * 204 | * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 205 | 206 | TAILQ_HEAD(timeout_list, timeout); 207 | 208 | struct timeouts { 209 | struct timeout_list wheel[WHEEL_NUM][WHEEL_LEN], expired; 210 | 211 | wheel_t pending[WHEEL_NUM]; 212 | 213 | timeout_t curtime; 214 | timeout_t hertz; 215 | }; /* struct timeouts */ 216 | 217 | 218 | static struct timeouts *timeouts_init(struct timeouts *T, timeout_t hz) { 219 | unsigned i, j; 220 | 221 | for (i = 0; i < countof(T->wheel); i++) { 222 | for (j = 0; j < countof(T->wheel[i]); j++) { 223 | TAILQ_INIT(&T->wheel[i][j]); 224 | } 225 | } 226 | 227 | TAILQ_INIT(&T->expired); 228 | 229 | for (i = 0; i < countof(T->pending); i++) { 230 | T->pending[i] = 0; 231 | } 232 | 233 | T->curtime = 0; 234 | T->hertz = (hz)? hz : TIMEOUT_mHZ; 235 | 236 | return T; 237 | } /* timeouts_init() */ 238 | 239 | 240 | TIMEOUT_PUBLIC struct timeouts *timeouts_open(timeout_t hz, int *error) { 241 | struct timeouts *T; 242 | 243 | if ((T = malloc(sizeof *T))) 244 | return timeouts_init(T, hz); 245 | 246 | *error = errno; 247 | 248 | return NULL; 249 | } /* timeouts_open() */ 250 | 251 | 252 | static void timeouts_reset(struct timeouts *T) { 253 | struct timeout_list reset; 254 | struct timeout *to; 255 | unsigned i, j; 256 | 257 | TAILQ_INIT(&reset); 258 | 259 | for (i = 0; i < countof(T->wheel); i++) { 260 | for (j = 0; j < countof(T->wheel[i]); j++) { 261 | TAILQ_CONCAT(&reset, &T->wheel[i][j], tqe); 262 | } 263 | } 264 | 265 | TAILQ_CONCAT(&reset, &T->expired, tqe); 266 | 267 | TAILQ_FOREACH(to, &reset, tqe) { 268 | to->pending = NULL; 269 | TO_SET_TIMEOUTS(to, NULL); 270 | } 271 | } /* timeouts_reset() */ 272 | 273 | 274 | TIMEOUT_PUBLIC void timeouts_close(struct timeouts *T) { 275 | /* 276 | * NOTE: Delete installed timeouts so timeout_pending() and 277 | * timeout_expired() worked as expected. 278 | */ 279 | timeouts_reset(T); 280 | 281 | free(T); 282 | } /* timeouts_close() */ 283 | 284 | 285 | TIMEOUT_PUBLIC timeout_t timeouts_hz(struct timeouts *T) { 286 | return T->hertz; 287 | } /* timeouts_hz() */ 288 | 289 | 290 | TIMEOUT_PUBLIC void timeouts_del(struct timeouts *T, struct timeout *to) { 291 | if (to->pending) { 292 | TAILQ_REMOVE(to->pending, to, tqe); 293 | 294 | if (to->pending != &T->expired && TAILQ_EMPTY(to->pending)) { 295 | ptrdiff_t index = to->pending - &T->wheel[0][0]; 296 | int wheel = index / WHEEL_LEN; 297 | int slot = index % WHEEL_LEN; 298 | 299 | T->pending[wheel] &= ~(WHEEL_C(1) << slot); 300 | } 301 | 302 | to->pending = NULL; 303 | TO_SET_TIMEOUTS(to, NULL); 304 | } 305 | } /* timeouts_del() */ 306 | 307 | 308 | static inline reltime_t timeout_rem(struct timeouts *T, struct timeout *to) { 309 | return to->expires - T->curtime; 310 | } /* timeout_rem() */ 311 | 312 | 313 | static inline int timeout_wheel(timeout_t timeout) { 314 | /* must be called with timeout != 0, so fls input is nonzero */ 315 | return (fls(MIN(timeout, TIMEOUT_MAX)) - 1) / WHEEL_BIT; 316 | } /* timeout_wheel() */ 317 | 318 | 319 | static inline int timeout_slot(int wheel, timeout_t expires) { 320 | return WHEEL_MASK & ((expires >> (wheel * WHEEL_BIT)) - !!wheel); 321 | } /* timeout_slot() */ 322 | 323 | 324 | static void timeouts_sched(struct timeouts *T, struct timeout *to, timeout_t expires) { 325 | timeout_t rem; 326 | int wheel, slot; 327 | 328 | timeouts_del(T, to); 329 | 330 | to->expires = expires; 331 | 332 | TO_SET_TIMEOUTS(to, T); 333 | 334 | if (expires > T->curtime) { 335 | rem = timeout_rem(T, to); 336 | 337 | /* rem is nonzero since: 338 | * rem == timeout_rem(T,to), 339 | * == to->expires - T->curtime 340 | * and above we have expires > T->curtime. 341 | */ 342 | wheel = timeout_wheel(rem); 343 | slot = timeout_slot(wheel, to->expires); 344 | 345 | to->pending = &T->wheel[wheel][slot]; 346 | TAILQ_INSERT_TAIL(to->pending, to, tqe); 347 | 348 | T->pending[wheel] |= WHEEL_C(1) << slot; 349 | } else { 350 | to->pending = &T->expired; 351 | TAILQ_INSERT_TAIL(to->pending, to, tqe); 352 | } 353 | } /* timeouts_sched() */ 354 | 355 | 356 | #ifndef TIMEOUT_DISABLE_INTERVALS 357 | static void timeouts_readd(struct timeouts *T, struct timeout *to) { 358 | to->expires += to->interval; 359 | 360 | if (to->expires <= T->curtime) { 361 | /* If we've missed the next firing of this timeout, reschedule 362 | * it to occur at the next multiple of its interval after 363 | * the last time that it fired. 364 | */ 365 | timeout_t n = T->curtime - to->expires; 366 | timeout_t r = n % to->interval; 367 | to->expires = T->curtime + (to->interval - r); 368 | } 369 | 370 | timeouts_sched(T, to, to->expires); 371 | } /* timeouts_readd() */ 372 | #endif 373 | 374 | 375 | TIMEOUT_PUBLIC void timeouts_add(struct timeouts *T, struct timeout *to, timeout_t timeout) { 376 | #ifndef TIMEOUT_DISABLE_INTERVALS 377 | if (to->flags & TIMEOUT_INT) 378 | to->interval = MAX(1, timeout); 379 | #endif 380 | 381 | if (to->flags & TIMEOUT_ABS) 382 | timeouts_sched(T, to, timeout); 383 | else 384 | timeouts_sched(T, to, T->curtime + timeout); 385 | } /* timeouts_add() */ 386 | 387 | 388 | TIMEOUT_PUBLIC void timeouts_update(struct timeouts *T, abstime_t curtime) { 389 | timeout_t elapsed = curtime - T->curtime; 390 | struct timeout_list todo; 391 | int wheel; 392 | 393 | TAILQ_INIT(&todo); 394 | 395 | /* 396 | * There's no avoiding looping over every wheel. It's best to keep 397 | * WHEEL_NUM smallish. 398 | */ 399 | for (wheel = 0; wheel < WHEEL_NUM; wheel++) { 400 | wheel_t pending; 401 | 402 | /* 403 | * Calculate the slots expiring in this wheel 404 | * 405 | * If the elapsed time is greater than the maximum period of 406 | * the wheel, mark every position as expiring. 407 | * 408 | * Otherwise, to determine the expired slots fill in all the 409 | * bits between the last slot processed and the current 410 | * slot, inclusive of the last slot. We'll bitwise-AND this 411 | * with our pending set below. 412 | * 413 | * If a wheel rolls over, force a tick of the next higher 414 | * wheel. 415 | */ 416 | if ((elapsed >> (wheel * WHEEL_BIT)) > WHEEL_MAX) { 417 | pending = (wheel_t)~WHEEL_C(0); 418 | } else { 419 | wheel_t _elapsed = WHEEL_MASK & (elapsed >> (wheel * WHEEL_BIT)); 420 | int oslot, nslot; 421 | 422 | /* 423 | * TODO: It's likely that at least one of the 424 | * following three bit fill operations is redundant 425 | * or can be replaced with a simpler operation. 426 | */ 427 | oslot = WHEEL_MASK & (T->curtime >> (wheel * WHEEL_BIT)); 428 | pending = rotl(((UINT64_C(1) << _elapsed) - 1), oslot); 429 | 430 | nslot = WHEEL_MASK & (curtime >> (wheel * WHEEL_BIT)); 431 | pending |= rotr(rotl(((WHEEL_C(1) << _elapsed) - 1), nslot), _elapsed); 432 | pending |= WHEEL_C(1) << nslot; 433 | } 434 | 435 | while (pending & T->pending[wheel]) { 436 | /* ctz input cannot be zero: loop condition. */ 437 | int slot = ctz(pending & T->pending[wheel]); 438 | TAILQ_CONCAT(&todo, &T->wheel[wheel][slot], tqe); 439 | T->pending[wheel] &= ~(UINT64_C(1) << slot); 440 | } 441 | 442 | if (!(0x1 & pending)) 443 | break; /* break if we didn't wrap around end of wheel */ 444 | 445 | /* if we're continuing, the next wheel must tick at least once */ 446 | elapsed = MAX(elapsed, (WHEEL_LEN << (wheel * WHEEL_BIT))); 447 | } 448 | 449 | T->curtime = curtime; 450 | 451 | while (!TAILQ_EMPTY(&todo)) { 452 | struct timeout *to = TAILQ_FIRST(&todo); 453 | 454 | TAILQ_REMOVE(&todo, to, tqe); 455 | to->pending = NULL; 456 | 457 | timeouts_sched(T, to, to->expires); 458 | } 459 | 460 | return; 461 | } /* timeouts_update() */ 462 | 463 | 464 | TIMEOUT_PUBLIC void timeouts_step(struct timeouts *T, reltime_t elapsed) { 465 | timeouts_update(T, T->curtime + elapsed); 466 | } /* timeouts_step() */ 467 | 468 | 469 | TIMEOUT_PUBLIC bool timeouts_pending(struct timeouts *T) { 470 | wheel_t pending = 0; 471 | int wheel; 472 | 473 | for (wheel = 0; wheel < WHEEL_NUM; wheel++) { 474 | pending |= T->pending[wheel]; 475 | } 476 | 477 | return !!pending; 478 | } /* timeouts_pending() */ 479 | 480 | 481 | TIMEOUT_PUBLIC bool timeouts_expired(struct timeouts *T) { 482 | return !TAILQ_EMPTY(&T->expired); 483 | } /* timeouts_expired() */ 484 | 485 | 486 | /* 487 | * Calculate the interval before needing to process any timeouts pending on 488 | * any wheel. 489 | * 490 | * (This is separated from the public API routine so we can evaluate our 491 | * wheel invariant assertions irrespective of the expired queue.) 492 | * 493 | * This might return a timeout value sooner than any installed timeout if 494 | * only higher-order wheels have timeouts pending. We can only know when to 495 | * process a wheel, not precisely when a timeout is scheduled. Our timeout 496 | * accuracy could be off by 2^(N*M)-1 units where N is the wheel number and 497 | * M is WHEEL_BIT. Only timeouts which have fallen through to wheel 0 can be 498 | * known exactly. 499 | * 500 | * We should never return a timeout larger than the lowest actual timeout. 501 | */ 502 | static timeout_t timeouts_int(struct timeouts *T) { 503 | timeout_t timeout = ~TIMEOUT_C(0), _timeout; 504 | timeout_t relmask; 505 | int wheel, slot; 506 | 507 | relmask = 0; 508 | 509 | for (wheel = 0; wheel < WHEEL_NUM; wheel++) { 510 | if (T->pending[wheel]) { 511 | slot = WHEEL_MASK & (T->curtime >> (wheel * WHEEL_BIT)); 512 | 513 | /* ctz input cannot be zero: T->pending[wheel] is 514 | * nonzero, so rotr() is nonzero. */ 515 | _timeout = (ctz(rotr(T->pending[wheel], slot)) + !!wheel) << (wheel * WHEEL_BIT); 516 | /* +1 to higher order wheels as those timeouts are one rotation in the future (otherwise they'd be on a lower wheel or expired) */ 517 | 518 | _timeout -= relmask & T->curtime; 519 | /* reduce by how much lower wheels have progressed */ 520 | 521 | timeout = MIN(_timeout, timeout); 522 | } 523 | 524 | relmask <<= WHEEL_BIT; 525 | relmask |= WHEEL_MASK; 526 | } 527 | 528 | return timeout; 529 | } /* timeouts_int() */ 530 | 531 | 532 | /* 533 | * Calculate the interval our caller can wait before needing to process 534 | * events. 535 | */ 536 | TIMEOUT_PUBLIC timeout_t timeouts_timeout(struct timeouts *T) { 537 | if (!TAILQ_EMPTY(&T->expired)) 538 | return 0; 539 | 540 | return timeouts_int(T); 541 | } /* timeouts_timeout() */ 542 | 543 | 544 | TIMEOUT_PUBLIC struct timeout *timeouts_get(struct timeouts *T) { 545 | if (!TAILQ_EMPTY(&T->expired)) { 546 | struct timeout *to = TAILQ_FIRST(&T->expired); 547 | 548 | TAILQ_REMOVE(&T->expired, to, tqe); 549 | to->pending = NULL; 550 | TO_SET_TIMEOUTS(to, NULL); 551 | 552 | #ifndef TIMEOUT_DISABLE_INTERVALS 553 | if ((to->flags & TIMEOUT_INT) && to->interval > 0) 554 | timeouts_readd(T, to); 555 | #endif 556 | 557 | return to; 558 | } else { 559 | return 0; 560 | } 561 | } /* timeouts_get() */ 562 | 563 | 564 | /* 565 | * Use dumb looping to locate the earliest timeout pending on the wheel so 566 | * our invariant assertions can check the result of our optimized code. 567 | */ 568 | static struct timeout *timeouts_min(struct timeouts *T) { 569 | struct timeout *to, *min = NULL; 570 | unsigned i, j; 571 | 572 | for (i = 0; i < countof(T->wheel); i++) { 573 | for (j = 0; j < countof(T->wheel[i]); j++) { 574 | TAILQ_FOREACH(to, &T->wheel[i][j], tqe) { 575 | if (!min || to->expires < min->expires) 576 | min = to; 577 | } 578 | } 579 | } 580 | 581 | return min; 582 | } /* timeouts_min() */ 583 | 584 | 585 | /* 586 | * Check some basic algorithm invariants. If these invariants fail then 587 | * something is definitely broken. 588 | */ 589 | #define report(...) do { \ 590 | if ((fp)) \ 591 | fprintf(fp, __VA_ARGS__); \ 592 | } while (0) 593 | 594 | #define check(expr, ...) do { \ 595 | if (!(expr)) { \ 596 | report(__VA_ARGS__); \ 597 | return 0; \ 598 | } \ 599 | } while (0) 600 | 601 | TIMEOUT_PUBLIC bool timeouts_check(struct timeouts *T, FILE *fp) { 602 | timeout_t timeout; 603 | struct timeout *to; 604 | 605 | if ((to = timeouts_min(T))) { 606 | check(to->expires > T->curtime, "missed timeout (expires:%" TIMEOUT_PRIu " <= curtime:%" TIMEOUT_PRIu ")\n", to->expires, T->curtime); 607 | 608 | timeout = timeouts_int(T); 609 | 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); 610 | 611 | timeout = timeouts_timeout(T); 612 | 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); 613 | } else { 614 | timeout = timeouts_timeout(T); 615 | 616 | if (!TAILQ_EMPTY(&T->expired)) 617 | check(timeout == 0, "wrong soft timeout (soft:%" TIMEOUT_PRIu " != hard:%" TIMEOUT_PRIu ")\n", timeout, TIMEOUT_C(0)); 618 | else 619 | check(timeout == ~TIMEOUT_C(0), "wrong soft timeout (soft:%" TIMEOUT_PRIu " != hard:%" TIMEOUT_PRIu ")\n", timeout, ~TIMEOUT_C(0)); 620 | } 621 | 622 | return 1; 623 | } /* timeouts_check() */ 624 | 625 | 626 | #define ENTER \ 627 | do { \ 628 | static const int pc0 = __LINE__; \ 629 | switch (pc0 + it->pc) { \ 630 | case __LINE__: (void)0 631 | 632 | #define SAVE_AND_DO(do_statement) \ 633 | do { \ 634 | it->pc = __LINE__ - pc0; \ 635 | do_statement; \ 636 | case __LINE__: (void)0; \ 637 | } while (0) 638 | 639 | #define YIELD(rv) \ 640 | SAVE_AND_DO(return (rv)) 641 | 642 | #define LEAVE \ 643 | SAVE_AND_DO(break); \ 644 | } \ 645 | } while (0) 646 | 647 | TIMEOUT_PUBLIC struct timeout *timeouts_next(struct timeouts *T, struct timeouts_it *it) { 648 | struct timeout *to; 649 | 650 | ENTER; 651 | 652 | if (it->flags & TIMEOUTS_EXPIRED) { 653 | if (it->flags & TIMEOUTS_CLEAR) { 654 | while ((to = timeouts_get(T))) { 655 | YIELD(to); 656 | } 657 | } else { 658 | TAILQ_FOREACH_SAFE(to, &T->expired, tqe, it->to) { 659 | YIELD(to); 660 | } 661 | } 662 | } 663 | 664 | if (it->flags & TIMEOUTS_PENDING) { 665 | for (it->i = 0; it->i < countof(T->wheel); it->i++) { 666 | for (it->j = 0; it->j < countof(T->wheel[it->i]); it->j++) { 667 | TAILQ_FOREACH_SAFE(to, &T->wheel[it->i][it->j], tqe, it->to) { 668 | YIELD(to); 669 | } 670 | } 671 | } 672 | } 673 | 674 | LEAVE; 675 | 676 | return NULL; 677 | } /* timeouts_next */ 678 | 679 | #undef LEAVE 680 | #undef YIELD 681 | #undef SAVE_AND_DO 682 | #undef ENTER 683 | 684 | 685 | /* 686 | * T I M E O U T R O U T I N E S 687 | * 688 | * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 689 | 690 | TIMEOUT_PUBLIC struct timeout *timeout_init(struct timeout *to, int flags) { 691 | memset(to, 0, sizeof *to); 692 | 693 | to->flags = flags; 694 | 695 | return to; 696 | } /* timeout_init() */ 697 | 698 | 699 | #ifndef TIMEOUT_DISABLE_RELATIVE_ACCESS 700 | TIMEOUT_PUBLIC bool timeout_pending(struct timeout *to) { 701 | return to->pending && to->pending != &to->timeouts->expired; 702 | } /* timeout_pending() */ 703 | 704 | 705 | TIMEOUT_PUBLIC bool timeout_expired(struct timeout *to) { 706 | return to->pending && to->pending == &to->timeouts->expired; 707 | } /* timeout_expired() */ 708 | 709 | 710 | TIMEOUT_PUBLIC void timeout_del(struct timeout *to) { 711 | timeouts_del(to->timeouts, to); 712 | } /* timeout_del() */ 713 | #endif 714 | 715 | 716 | /* 717 | * V E R S I O N I N T E R F A C E S 718 | * 719 | * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 720 | 721 | TIMEOUT_PUBLIC int timeout_version(void) { 722 | return TIMEOUT_VERSION; 723 | } /* timeout_version() */ 724 | 725 | 726 | TIMEOUT_PUBLIC const char *timeout_vendor(void) { 727 | return TIMEOUT_VENDOR; 728 | } /* timeout_version() */ 729 | 730 | 731 | TIMEOUT_PUBLIC int timeout_v_rel(void) { 732 | return TIMEOUT_V_REL; 733 | } /* timeout_version() */ 734 | 735 | 736 | TIMEOUT_PUBLIC int timeout_v_abi(void) { 737 | return TIMEOUT_V_ABI; 738 | } /* timeout_version() */ 739 | 740 | 741 | TIMEOUT_PUBLIC int timeout_v_api(void) { 742 | return TIMEOUT_V_API; 743 | } /* timeout_version() */ 744 | 745 | -------------------------------------------------------------------------------- /timeout.h: -------------------------------------------------------------------------------- 1 | /* ========================================================================== 2 | * timeout.h - Tickless hierarchical timing wheel. 3 | * -------------------------------------------------------------------------- 4 | * Copyright (c) 2013, 2014 William Ahern 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a 7 | * copy of this software and associated documentation files (the 8 | * "Software"), to deal in the Software without restriction, including 9 | * without limitation the rights to use, copy, modify, merge, publish, 10 | * distribute, sublicense, and/or sell copies of the Software, and to permit 11 | * persons to whom the Software is furnished to do so, subject to the 12 | * following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included 15 | * in all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 18 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 20 | * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 21 | * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 22 | * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 23 | * USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | * ========================================================================== 25 | */ 26 | #ifndef TIMEOUT_H 27 | #define TIMEOUT_H 28 | 29 | #include /* bool */ 30 | #include /* FILE */ 31 | 32 | #include /* PRIu64 PRIx64 PRIX64 uint64_t */ 33 | 34 | #include /* TAILQ(3) */ 35 | 36 | 37 | /* 38 | * V E R S I O N I N T E R F A C E S 39 | * 40 | * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 41 | 42 | #if !defined TIMEOUT_PUBLIC 43 | #define TIMEOUT_PUBLIC 44 | #endif 45 | 46 | #define TIMEOUT_VERSION TIMEOUT_V_REL 47 | #define TIMEOUT_VENDOR "william@25thandClement.com" 48 | 49 | #define TIMEOUT_V_REL 0x20160226 50 | #define TIMEOUT_V_ABI 0x20160224 51 | #define TIMEOUT_V_API 0x20160226 52 | 53 | TIMEOUT_PUBLIC int timeout_version(void); 54 | 55 | TIMEOUT_PUBLIC const char *timeout_vendor(void); 56 | 57 | TIMEOUT_PUBLIC int timeout_v_rel(void); 58 | 59 | TIMEOUT_PUBLIC int timeout_v_abi(void); 60 | 61 | TIMEOUT_PUBLIC int timeout_v_api(void); 62 | 63 | 64 | /* 65 | * I N T E G E R T Y P E I N T E R F A C E S 66 | * 67 | * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 68 | 69 | #define TIMEOUT_C(n) UINT64_C(n) 70 | #define TIMEOUT_PRIu PRIu64 71 | #define TIMEOUT_PRIx PRIx64 72 | #define TIMEOUT_PRIX PRIX64 73 | 74 | #define TIMEOUT_mHZ TIMEOUT_C(1000) 75 | #define TIMEOUT_uHZ TIMEOUT_C(1000000) 76 | #define TIMEOUT_nHZ TIMEOUT_C(1000000000) 77 | 78 | typedef uint64_t timeout_t; 79 | 80 | #define timeout_error_t int /* for documentation purposes */ 81 | 82 | 83 | /* 84 | * C A L L B A C K I N T E R F A C E 85 | * 86 | * Callback function parameters unspecified to make embedding into existing 87 | * applications easier. 88 | * 89 | * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 90 | 91 | #ifndef TIMEOUT_CB_OVERRIDE 92 | struct timeout_cb { 93 | void (*fn)(); 94 | void *arg; 95 | }; /* struct timeout_cb */ 96 | #endif 97 | 98 | /* 99 | * T I M E O U T I N T E R F A C E S 100 | * 101 | * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 102 | 103 | #ifndef TIMEOUT_DISABLE_INTERVALS 104 | #define TIMEOUT_INT 0x01 /* interval (repeating) timeout */ 105 | #endif 106 | #define TIMEOUT_ABS 0x02 /* treat timeout values as absolute */ 107 | 108 | #define TIMEOUT_INITIALIZER(flags) { (flags) } 109 | 110 | #define timeout_setcb(to, fn, arg) do { \ 111 | (to)->callback.fn = (fn); \ 112 | (to)->callback.arg = (arg); \ 113 | } while (0) 114 | 115 | struct timeout { 116 | int flags; 117 | 118 | timeout_t expires; 119 | /* absolute expiration time */ 120 | 121 | struct timeout_list *pending; 122 | /* timeout list if pending on wheel or expiry queue */ 123 | 124 | TAILQ_ENTRY(timeout) tqe; 125 | /* entry member for struct timeout_list lists */ 126 | 127 | #ifndef TIMEOUT_DISABLE_CALLBACKS 128 | struct timeout_cb callback; 129 | /* optional callback information */ 130 | #endif 131 | 132 | #ifndef TIMEOUT_DISABLE_INTERVALS 133 | timeout_t interval; 134 | /* timeout interval if periodic */ 135 | #endif 136 | 137 | #ifndef TIMEOUT_DISABLE_RELATIVE_ACCESS 138 | struct timeouts *timeouts; 139 | /* timeouts collection if member of */ 140 | #endif 141 | }; /* struct timeout */ 142 | 143 | 144 | TIMEOUT_PUBLIC struct timeout *timeout_init(struct timeout *, int); 145 | /* initialize timeout structure (same as TIMEOUT_INITIALIZER) */ 146 | 147 | #ifndef TIMEOUT_DISABLE_RELATIVE_ACCESS 148 | TIMEOUT_PUBLIC bool timeout_pending(struct timeout *); 149 | /* true if on timing wheel, false otherwise */ 150 | 151 | TIMEOUT_PUBLIC bool timeout_expired(struct timeout *); 152 | /* true if on expired queue, false otherwise */ 153 | 154 | TIMEOUT_PUBLIC void timeout_del(struct timeout *); 155 | /* remove timeout from any timing wheel (okay if not member of any) */ 156 | #endif 157 | 158 | /* 159 | * T I M I N G W H E E L I N T E R F A C E S 160 | * 161 | * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 162 | 163 | struct timeouts; 164 | 165 | TIMEOUT_PUBLIC struct timeouts *timeouts_open(timeout_t, timeout_error_t *); 166 | /* open a new timing wheel, setting optional HZ (for float conversions) */ 167 | 168 | TIMEOUT_PUBLIC void timeouts_close(struct timeouts *); 169 | /* destroy timing wheel */ 170 | 171 | TIMEOUT_PUBLIC timeout_t timeouts_hz(struct timeouts *); 172 | /* return HZ setting (for float conversions) */ 173 | 174 | TIMEOUT_PUBLIC void timeouts_update(struct timeouts *, timeout_t); 175 | /* update timing wheel with current absolute time */ 176 | 177 | TIMEOUT_PUBLIC void timeouts_step(struct timeouts *, timeout_t); 178 | /* step timing wheel by relative time */ 179 | 180 | TIMEOUT_PUBLIC timeout_t timeouts_timeout(struct timeouts *); 181 | /* return interval to next required update */ 182 | 183 | TIMEOUT_PUBLIC void timeouts_add(struct timeouts *, struct timeout *, timeout_t); 184 | /* add timeout to timing wheel */ 185 | 186 | TIMEOUT_PUBLIC void timeouts_del(struct timeouts *, struct timeout *); 187 | /* remove timeout from any timing wheel or expired queue (okay if on neither) */ 188 | 189 | TIMEOUT_PUBLIC struct timeout *timeouts_get(struct timeouts *); 190 | /* return any expired timeout (caller should loop until NULL-return) */ 191 | 192 | TIMEOUT_PUBLIC bool timeouts_pending(struct timeouts *); 193 | /* return true if any timeouts pending on timing wheel */ 194 | 195 | TIMEOUT_PUBLIC bool timeouts_expired(struct timeouts *); 196 | /* return true if any timeouts on expired queue */ 197 | 198 | TIMEOUT_PUBLIC bool timeouts_check(struct timeouts *, FILE *); 199 | /* return true if invariants hold. describes failures to optional file handle. */ 200 | 201 | #define TIMEOUTS_PENDING 0x10 202 | #define TIMEOUTS_EXPIRED 0x20 203 | #define TIMEOUTS_ALL (TIMEOUTS_PENDING|TIMEOUTS_EXPIRED) 204 | #define TIMEOUTS_CLEAR 0x40 205 | 206 | #define TIMEOUTS_IT_INITIALIZER(flags) { (flags), 0, 0, 0, 0 } 207 | 208 | #define TIMEOUTS_IT_INIT(cur, _flags) do { \ 209 | (cur)->flags = (_flags); \ 210 | (cur)->pc = 0; \ 211 | } while (0) 212 | 213 | struct timeouts_it { 214 | int flags; 215 | unsigned pc, i, j; 216 | struct timeout *to; 217 | }; /* struct timeouts_it */ 218 | 219 | TIMEOUT_PUBLIC struct timeout *timeouts_next(struct timeouts *, struct timeouts_it *); 220 | /* return next timeout in pending wheel or expired queue. caller can delete 221 | * the returned timeout, but should not otherwise manipulate the timing 222 | * wheel. in particular, caller SHOULD NOT delete any other timeout as that 223 | * could invalidate cursor state and trigger a use-after-free. 224 | */ 225 | 226 | #define TIMEOUTS_FOREACH(var, T, flags) \ 227 | struct timeouts_it _it = TIMEOUTS_IT_INITIALIZER((flags)); \ 228 | while (((var) = timeouts_next((T), &_it))) 229 | 230 | 231 | /* 232 | * B O N U S W H E E L I N T E R F A C E S 233 | * 234 | * I usually use floating point timeouts in all my code, but it's cleaner to 235 | * separate it to keep the core algorithmic code simple. 236 | * 237 | * Using macros instead of static inline routines where routines 238 | * might be used to keep -lm linking optional. 239 | * 240 | * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 241 | 242 | #include /* ceil(3) */ 243 | 244 | #define timeouts_f2i(T, f) \ 245 | ((timeout_t)ceil((f) * timeouts_hz((T)))) /* prefer late expiration over early */ 246 | 247 | #define timeouts_i2f(T, i) \ 248 | ((double)(i) / timeouts_hz((T))) 249 | 250 | #define timeouts_addf(T, to, timeout) \ 251 | timeouts_add((T), (to), timeouts_f2i((T), (timeout))) 252 | 253 | #endif /* TIMEOUT_H */ 254 | --------------------------------------------------------------------------------