Browse Source
Устранены баги потери событий: - timeout_ms = -1 при истёкшем таймере (poll висел бесконечно) - stale now_ms в process_timeouts (таймеры пропускались на одну итерацию) Замена: - timeout_heap.c/h удалены - Добавлен twheel — tickless hierarchical timing wheel (WHEEL_BIT=6, WHEEL_NUM=4) на основе реализации William Ahern (MIT), адаптирован под u_malloc/u_free/DEBUG_* - struct timeout_node: struct twheel to первым полем, убран heap_index/expiration_ms - uasync_set_timeout: twheel_init + twheels_add(TIMEOUT_ABS) - uasync_cancel_timeout: twheels_del + немедленный free (O(1)) - process_timeouts: twheels_update + цикл twheels_get - get_next_timeout: twheels_timeout() — всегда актуален - uasync_poll: исправлена логика расчёта timeout_mstmo
13 changed files with 433 additions and 485 deletions
@ -1,193 +0,0 @@
|
||||
// timeout_heap.c
|
||||
|
||||
#include "timeout_heap.h" |
||||
#include "debug_config.h" |
||||
#include <stdlib.h> |
||||
#include <stdio.h> // For potential error printing, optional |
||||
#include "mem.h" |
||||
|
||||
// Helper macros for 1-based indices
|
||||
#define PARENT(i) ((i) / 2) |
||||
#define LEFT_CHILD(i) (2 * (i)) |
||||
#define RIGHT_CHILD(i) (2 * (i) + 1) |
||||
|
||||
TimeoutHeap *timeout_heap_create(size_t initial_capacity) { |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "Creating TH1..."); |
||||
TimeoutHeap *h = u_malloc(sizeof(TimeoutHeap)); |
||||
if (!h) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "TH0 error..."); |
||||
return NULL; |
||||
} |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "Creating TH2..."); |
||||
h->heap = u_malloc(sizeof(TimeoutEntry) * initial_capacity); |
||||
if (!h->heap) { |
||||
DEBUG_ERROR(DEBUG_CATEGORY_ETCP, "TH1 error..."); |
||||
u_free(h); |
||||
return NULL; |
||||
} |
||||
DEBUG_DEBUG(DEBUG_CATEGORY_ETCP, "Creating TH3..."); |
||||
h->size = 0; |
||||
h->capacity = initial_capacity; |
||||
h->freed_count = 0; |
||||
h->user_data = NULL; |
||||
h->free_callback = NULL; |
||||
return h; |
||||
} |
||||
|
||||
|
||||
void timeout_heap_destroy(TimeoutHeap *h) { |
||||
if (!h) return; |
||||
|
||||
// Free all remaining data (deleted or not)
|
||||
for (size_t i = 0; i < h->size; i++) { |
||||
if (h->free_callback) { |
||||
h->free_callback(h->user_data, h->heap[i].data); |
||||
} |
||||
} |
||||
|
||||
u_free(h->heap); |
||||
u_free(h); |
||||
} |
||||
|
||||
|
||||
static void update_index(TimeoutHeap *h, size_t idx) { |
||||
if (h->heap[idx].index_ptr) |
||||
*h->heap[idx].index_ptr = idx; |
||||
} |
||||
|
||||
static void swap_entries(TimeoutHeap *h, size_t a, size_t b) { |
||||
TimeoutEntry temp = h->heap[a]; |
||||
h->heap[a] = h->heap[b]; |
||||
h->heap[b] = temp; |
||||
update_index(h, a); |
||||
update_index(h, b); |
||||
} |
||||
|
||||
static void bubble_up(TimeoutHeap *h, size_t i) { |
||||
// i is 1-based
|
||||
while (i > 1 && h->heap[PARENT(i) - 1].expiration > h->heap[i - 1].expiration) { |
||||
swap_entries(h, PARENT(i) - 1, i - 1); |
||||
i = PARENT(i); |
||||
} |
||||
} |
||||
|
||||
int timeout_heap_push(TimeoutHeap *h, TimeoutTime expiration, void *data, size_t *index_ptr) { |
||||
if (h->size == h->capacity) { |
||||
size_t new_cap = h->capacity ? h->capacity * 2 : 1; |
||||
TimeoutEntry *new_heap = u_realloc(h->heap, sizeof(TimeoutEntry) * new_cap); |
||||
if (!new_heap) return -1; // Allocation failed
|
||||
h->heap = new_heap; |
||||
h->capacity = new_cap; |
||||
} |
||||
|
||||
// Insert at end (0-based)
|
||||
size_t idx = h->size++; |
||||
h->heap[idx].expiration = expiration; |
||||
h->heap[idx].data = data; |
||||
h->heap[idx].index_ptr = index_ptr; |
||||
h->heap[idx].deleted = 0; |
||||
if (index_ptr) |
||||
*index_ptr = idx; |
||||
|
||||
// Bubble up (1-based)
|
||||
bubble_up(h, idx + 1); |
||||
return 0; |
||||
} |
||||
|
||||
static void heapify_down(TimeoutHeap *h, size_t i) { |
||||
// i is 1-based
|
||||
while (1) { |
||||
size_t smallest = i; |
||||
size_t left = LEFT_CHILD(i); |
||||
size_t right = RIGHT_CHILD(i); |
||||
|
||||
if (left <= h->size && h->heap[left - 1].expiration < h->heap[smallest - 1].expiration) { |
||||
smallest = left; |
||||
} |
||||
if (right <= h->size && h->heap[right - 1].expiration < h->heap[smallest - 1].expiration) { |
||||
smallest = right; |
||||
} |
||||
if (smallest == i) break; |
||||
|
||||
swap_entries(h, smallest - 1, i - 1); |
||||
i = smallest; |
||||
} |
||||
} |
||||
|
||||
static void remove_root(TimeoutHeap *h) { |
||||
if (h->size == 0) return; |
||||
|
||||
// Move last to root
|
||||
h->heap[0] = h->heap[--h->size]; |
||||
update_index(h, 0); |
||||
|
||||
// Heapify down (1-based)
|
||||
if (h->size > 0) { |
||||
heapify_down(h, 1); |
||||
} |
||||
} |
||||
|
||||
int timeout_heap_peek(TimeoutHeap *h, TimeoutEntry *out) { |
||||
if (h->size == 0) return -1; |
||||
|
||||
while (h->size > 0 && h->heap[0].deleted) { |
||||
void* data_to_free = h->heap[0].data; |
||||
remove_root(h); |
||||
if (h->free_callback) { |
||||
h->free_callback(h->user_data, data_to_free); |
||||
} |
||||
} |
||||
if (h->size == 0) return -1; |
||||
|
||||
*out = h->heap[0]; |
||||
return 0; |
||||
} |
||||
|
||||
int timeout_heap_pop(TimeoutHeap *h, TimeoutEntry *out) { |
||||
if (h->size == 0) return -1; |
||||
|
||||
while (h->size > 0 && h->heap[0].deleted) { |
||||
void* data_to_free = h->heap[0].data; |
||||
remove_root(h); |
||||
if (h->free_callback) { |
||||
h->free_callback(h->user_data, data_to_free); |
||||
} |
||||
} |
||||
if (h->size == 0) return -1; |
||||
|
||||
*out = h->heap[0]; |
||||
remove_root(h); |
||||
return 0; |
||||
} |
||||
|
||||
int timeout_heap_cancel(TimeoutHeap *h, TimeoutTime expiration, void *data) { |
||||
for (size_t i = 0; i < h->size; ++i) { |
||||
if (h->heap[i].expiration == expiration && h->heap[i].data == data) { |
||||
h->heap[i].deleted = 1; |
||||
h->heap[i].expiration = 0; |
||||
bubble_up(h, i + 1); |
||||
return 0; |
||||
} |
||||
} |
||||
return -1; // Not found
|
||||
} |
||||
|
||||
int timeout_heap_cancel_at(TimeoutHeap *h, size_t index, void *data) { |
||||
if (index >= h->size || h->heap[index].data != data) return -1; |
||||
|
||||
h->heap[index].deleted = 1; |
||||
h->heap[index].expiration = 0; |
||||
bubble_up(h, index + 1); |
||||
return 0; |
||||
} |
||||
|
||||
void timeout_heap_set_free_callback(TimeoutHeap *h, void* user_data, void (*callback)(void* user_data, void* data)) { |
||||
if (!h) return; |
||||
h->user_data = user_data; |
||||
h->free_callback = callback; |
||||
} |
||||
|
||||
size_t timeout_heap_get_size(TimeoutHeap *h) { |
||||
if (!h) return 0; |
||||
return h->size; |
||||
} |
||||
@ -1,97 +0,0 @@
|
||||
// timeout_heap.h
|
||||
|
||||
#ifndef TIMEOUT_HEAP_H |
||||
#define TIMEOUT_HEAP_H |
||||
|
||||
#include <stdint.h> // For uint64_t |
||||
#include <stddef.h> // For size_t |
||||
|
||||
typedef uint64_t TimeoutTime; // e.g., milliseconds since epoch or from now
|
||||
|
||||
typedef struct { |
||||
TimeoutTime expiration; // Sort key (smaller = earlier)
|
||||
void *data; // User data (e.g., callback or ID)
|
||||
size_t *index_ptr; // Pointer to node's heap_index (NULL = no tracking)
|
||||
int deleted; // 0 = active, 1 = deleted
|
||||
} TimeoutEntry; |
||||
|
||||
typedef struct TimeoutHeap TimeoutHeap; |
||||
|
||||
struct TimeoutHeap { |
||||
TimeoutEntry *heap; // Dynamic array
|
||||
size_t size; // Current number of elements
|
||||
size_t capacity; // Allocated size
|
||||
size_t freed_count; // Number of freed timer nodes
|
||||
void* user_data; // User data for free callback
|
||||
void (*free_callback)(void* user_data, void* data); // Callback to free data
|
||||
}; |
||||
|
||||
/**
|
||||
* Create a new timeout heap with initial capacity. |
||||
* @param initial_capacity Starting capacity (will grow as needed). |
||||
* @return Pointer to the heap, or NULL on failure. |
||||
*/ |
||||
TimeoutHeap *timeout_heap_create(size_t initial_capacity); |
||||
|
||||
/**
|
||||
* Destroy the timeout heap and free resources. |
||||
* @param h The heap to destroy. |
||||
*/ |
||||
void timeout_heap_destroy(TimeoutHeap *h); |
||||
|
||||
/**
|
||||
* Set a callback function to free data when deleted nodes are removed. |
||||
* @param h The heap. |
||||
* @param user_data User data passed to callback. |
||||
* @param callback Callback function (if NULL, data is freed with free()). |
||||
*/ |
||||
void timeout_heap_set_free_callback(TimeoutHeap *h, void* user_data, void (*callback)(void* user_data, void* data)); |
||||
|
||||
/**
|
||||
* Insert a new timeout into the heap. |
||||
* @param h The heap. |
||||
* @param expiration The expiration time. |
||||
* @param data User data associated with the timeout. |
||||
* @return 0 on success, -1 on allocation failure. |
||||
*/ |
||||
int timeout_heap_push(TimeoutHeap *h, TimeoutTime expiration, void *data, size_t *index_ptr); |
||||
|
||||
/**
|
||||
* Peek at the earliest non-deleted timeout without removing it. |
||||
* @param h The heap. |
||||
* @param out Where to store the entry. |
||||
* @return 0 on success, -1 if empty. |
||||
*/ |
||||
int timeout_heap_peek(TimeoutHeap *h, TimeoutEntry *out); |
||||
|
||||
/**
|
||||
* Pop the earliest non-deleted timeout from the heap. |
||||
* @param h The heap. |
||||
* @param out Where to store the entry. |
||||
* @return 0 on success, -1 if empty. |
||||
*/ |
||||
int timeout_heap_pop(TimeoutHeap *h, TimeoutEntry *out); |
||||
|
||||
/**
|
||||
* Cancel a timeout by matching expiration and data. |
||||
* Scans the heap linearly, so O(n) time. |
||||
* Assumes combinations are unique; cancels the first match. |
||||
* @param h The heap. |
||||
* @param expiration The expiration time to match. |
||||
* @param data The data to match. |
||||
* @return 0 if found and canceled, -1 if not found. |
||||
*/ |
||||
int timeout_heap_cancel(TimeoutHeap *h, TimeoutTime expiration, void *data); |
||||
|
||||
int timeout_heap_cancel_at(TimeoutHeap *h, size_t index, void *data); |
||||
|
||||
/**
|
||||
* Get the number of freed timer nodes. |
||||
* @param h The heap. |
||||
* @return Count of freed timer nodes. |
||||
*/ |
||||
size_t timeout_heap_get_freed_count(TimeoutHeap *h); |
||||
|
||||
size_t timeout_heap_get_size(TimeoutHeap *h); |
||||
|
||||
#endif // TIMEOUT_HEAP_H
|
||||
@ -0,0 +1,223 @@
|
||||
// twheel.c — Tickless hierarchical timing wheel (adapted from timeout.c)
|
||||
// Original: Copyright (c) 2013-2014 William Ahern, MIT license.
|
||||
|
||||
#include <limits.h> |
||||
#include <stddef.h> |
||||
#include <string.h> |
||||
#include <errno.h> |
||||
#include <sys/queue.h> |
||||
|
||||
#include "twheel.h" |
||||
#include "mem.h" |
||||
#include "debug_config.h" |
||||
|
||||
#define WHEEL_BIT 6 |
||||
#define WHEEL_NUM 4 |
||||
|
||||
#define WHEEL_LEN (1U << WHEEL_BIT) |
||||
#define WHEEL_MAX (WHEEL_LEN - 1) |
||||
#define WHEEL_MASK (WHEEL_LEN - 1) |
||||
|
||||
#include "twheel_bitops.c" |
||||
|
||||
#define ctz(n) ctz64(n) |
||||
#define clz(n) clz64(n) |
||||
#define fls(n) ((int)(64 - clz64(n))) |
||||
|
||||
typedef uint64_t wheel_t; |
||||
#define WHEEL_C(n) UINT64_C(n) |
||||
|
||||
#define countof(a) (sizeof(a) / sizeof *(a)) |
||||
|
||||
#ifndef TAILQ_CONCAT |
||||
#define TAILQ_CONCAT(head1, head2, field) do { \ |
||||
if (!TAILQ_EMPTY(head2)) { \
|
||||
*(head1)->tqh_last = (head2)->tqh_first; \
|
||||
(head2)->tqh_first->field.tqe_prev = (head1)->tqh_last; \
|
||||
(head1)->tqh_last = (head2)->tqh_last; \
|
||||
TAILQ_INIT((head2)); \
|
||||
} \
|
||||
} while (0) |
||||
#endif |
||||
|
||||
#ifndef TAILQ_FOREACH_SAFE |
||||
#define TAILQ_FOREACH_SAFE(var, head, field, tvar) \ |
||||
for ((var) = TAILQ_FIRST(head); \
|
||||
(var) && ((tvar) = TAILQ_NEXT(var, field), 1); \
|
||||
(var) = (tvar)) |
||||
#endif |
||||
|
||||
#ifndef MAX |
||||
#define MAX(a,b) (((a)>(b))?(a):(b)) |
||||
#endif |
||||
#ifndef MIN |
||||
#define MIN(a,b) (((a)<(b))?(a):(b)) |
||||
#endif |
||||
|
||||
static inline wheel_t rotl(const wheel_t v, int c) { |
||||
if (!(c &= (sizeof v * CHAR_BIT - 1))) return v; |
||||
return (v << c) | (v >> (sizeof v * CHAR_BIT - c)); |
||||
} |
||||
|
||||
static inline wheel_t rotr(const wheel_t v, int c) { |
||||
if (!(c &= (sizeof v * CHAR_BIT - 1))) return v; |
||||
return (v >> c) | (v << (sizeof v * CHAR_BIT - c)); |
||||
} |
||||
|
||||
static struct twheels *twheels_init(struct twheels *T, twheel_t hz) { |
||||
for (unsigned i = 0; i < WHEEL_NUM; i++) { |
||||
for (unsigned j = 0; j < WHEEL_LEN; j++) { |
||||
TAILQ_INIT(&T->wheel[i][j]); |
||||
} |
||||
} |
||||
TAILQ_INIT(&T->expired); |
||||
for (unsigned i = 0; i < WHEEL_NUM; i++) T->pending[i] = 0; |
||||
T->curtime = 0; |
||||
T->hertz = hz ? hz : TWHEEL_mHZ; |
||||
return T; |
||||
} |
||||
|
||||
struct twheels *twheels_open(twheel_t hz) { |
||||
struct twheels *T = u_malloc(sizeof *T); |
||||
if (!T) { DEBUG_ERROR(DEBUG_CATEGORY_TIMERS, "twheels_open: malloc failed"); return NULL; } |
||||
return twheels_init(T, hz); |
||||
} |
||||
|
||||
void twheels_close(struct twheels *T) { |
||||
if (!T) return; |
||||
struct twheel_list reset; |
||||
TAILQ_INIT(&reset); |
||||
for (unsigned i = 0; i < WHEEL_NUM; i++) |
||||
for (unsigned j = 0; j < WHEEL_LEN; j++) |
||||
TAILQ_CONCAT(&reset, &T->wheel[i][j], tqe); |
||||
TAILQ_CONCAT(&reset, &T->expired, tqe); |
||||
struct twheel *to; |
||||
TAILQ_FOREACH(to, &reset, tqe) to->pending = NULL; |
||||
u_free(T); |
||||
} |
||||
|
||||
twheel_t twheels_hz(struct twheels *T) { return T->hertz; } |
||||
|
||||
void twheels_del(struct twheels *T, struct twheel *to) { |
||||
if (to->pending) { |
||||
TAILQ_REMOVE(to->pending, to, tqe); |
||||
if (to->pending != &T->expired && TAILQ_EMPTY(to->pending)) { |
||||
ptrdiff_t index = to->pending - &T->wheel[0][0]; |
||||
int w = index / WHEEL_LEN, slot = index % WHEEL_LEN; |
||||
T->pending[w] &= ~(WHEEL_C(1) << slot); |
||||
} |
||||
to->pending = NULL; |
||||
} |
||||
} |
||||
|
||||
static inline int timeout_wheel(twheel_t timeout) { |
||||
return (fls(MIN(timeout, TWHEEL_MAX)) - 1) / WHEEL_BIT; |
||||
} |
||||
|
||||
static inline int timeout_slot(int wheel, twheel_t expires) { |
||||
return WHEEL_MASK & ((expires >> (wheel * WHEEL_BIT)) - !!wheel); |
||||
} |
||||
|
||||
static void twheels_sched(struct twheels *T, struct twheel *to, twheel_t expires) { |
||||
twheels_del(T, to); |
||||
to->expires = expires; |
||||
if (expires > T->curtime) { |
||||
twheel_t rem = expires - T->curtime; |
||||
int wheel = timeout_wheel(rem); |
||||
int slot = timeout_slot(wheel, to->expires); |
||||
to->pending = &T->wheel[wheel][slot]; |
||||
TAILQ_INSERT_TAIL(to->pending, to, tqe); |
||||
T->pending[wheel] |= WHEEL_C(1) << slot; |
||||
} else { |
||||
to->pending = &T->expired; |
||||
TAILQ_INSERT_TAIL(to->pending, to, tqe); |
||||
} |
||||
} |
||||
|
||||
void twheels_add(struct twheels *T, struct twheel *to, twheel_t timeout) { |
||||
if (to->flags & TWHEEL_INT) to->interval = MAX(1, timeout); |
||||
if (to->flags & TWHEEL_ABS) twheels_sched(T, to, timeout); |
||||
else twheels_sched(T, to, T->curtime + timeout); |
||||
} |
||||
|
||||
void twheels_update(struct twheels *T, twheel_t curtime) { |
||||
twheel_t elapsed = curtime - T->curtime; |
||||
struct twheel_list todo; |
||||
TAILQ_INIT(&todo); |
||||
|
||||
for (int wheel = 0; wheel < WHEEL_NUM; wheel++) { |
||||
wheel_t pending; |
||||
if ((elapsed >> (wheel * WHEEL_BIT)) > WHEEL_MAX) { |
||||
pending = (wheel_t)~WHEEL_C(0); |
||||
} else { |
||||
wheel_t _elapsed = WHEEL_MASK & (elapsed >> (wheel * WHEEL_BIT)); |
||||
int oslot = WHEEL_MASK & (T->curtime >> (wheel * WHEEL_BIT)); |
||||
pending = rotl(((UINT64_C(1) << _elapsed) - 1), oslot); |
||||
int nslot = WHEEL_MASK & (curtime >> (wheel * WHEEL_BIT)); |
||||
pending |= rotr(rotl(((WHEEL_C(1) << _elapsed) - 1), nslot), _elapsed); |
||||
pending |= WHEEL_C(1) << nslot; |
||||
} |
||||
while (pending & T->pending[wheel]) { |
||||
int slot = ctz(pending & T->pending[wheel]); |
||||
TAILQ_CONCAT(&todo, &T->wheel[wheel][slot], tqe); |
||||
T->pending[wheel] &= ~(UINT64_C(1) << slot); |
||||
} |
||||
if (!(0x1 & pending)) break; |
||||
elapsed = MAX(elapsed, (WHEEL_LEN << (wheel * WHEEL_BIT))); |
||||
} |
||||
T->curtime = curtime; |
||||
|
||||
struct twheel *to; |
||||
while (!TAILQ_EMPTY(&todo)) { |
||||
to = TAILQ_FIRST(&todo); |
||||
TAILQ_REMOVE(&todo, to, tqe); |
||||
to->pending = NULL; |
||||
twheels_sched(T, to, to->expires); |
||||
} |
||||
} |
||||
|
||||
bool twheels_pending(struct twheels *T) { |
||||
wheel_t pending = 0; |
||||
for (int wheel = 0; wheel < WHEEL_NUM; wheel++) pending |= T->pending[wheel]; |
||||
return !!pending; |
||||
} |
||||
|
||||
bool twheels_expired(struct twheels *T) { return !TAILQ_EMPTY(&T->expired); } |
||||
|
||||
static twheel_t twheels_int(struct twheels *T) { |
||||
twheel_t timeout = ~TWHEEL_C(0), _timeout, relmask = 0; |
||||
for (int wheel = 0; wheel < WHEEL_NUM; wheel++) { |
||||
if (T->pending[wheel]) { |
||||
int slot = WHEEL_MASK & (T->curtime >> (wheel * WHEEL_BIT)); |
||||
_timeout = (ctz(rotr(T->pending[wheel], slot)) + !!wheel) << (wheel * WHEEL_BIT); |
||||
_timeout -= relmask & T->curtime; |
||||
timeout = MIN(_timeout, timeout); |
||||
} |
||||
relmask <<= WHEEL_BIT; |
||||
relmask |= WHEEL_MASK; |
||||
} |
||||
return timeout; |
||||
} |
||||
|
||||
twheel_t twheels_timeout(struct twheels *T) { |
||||
if (!TAILQ_EMPTY(&T->expired)) return 0; |
||||
return twheels_int(T); |
||||
} |
||||
|
||||
struct twheel *twheels_get(struct twheels *T) { |
||||
if (!TAILQ_EMPTY(&T->expired)) { |
||||
struct twheel *to = TAILQ_FIRST(&T->expired); |
||||
TAILQ_REMOVE(&T->expired, to, tqe); |
||||
to->pending = NULL; |
||||
return to; |
||||
} |
||||
return NULL; |
||||
} |
||||
|
||||
struct twheel *twheel_init(struct twheel *to, int flags) { |
||||
memset(to, 0, sizeof *to); |
||||
to->flags = flags; |
||||
return to; |
||||
} |
||||
|
||||
bool twheel_pending(struct twheel *to) { return to->pending != NULL; } |
||||
@ -0,0 +1,57 @@
|
||||
// twheel.h — Tickless hierarchical timing wheel (adapted from timeout.h)
|
||||
// Original: Copyright (c) 2013-2014 William Ahern, MIT license.
|
||||
|
||||
#ifndef TWHEEL_H |
||||
#define TWHEEL_H |
||||
|
||||
#include <stdbool.h> |
||||
#include <stdint.h> |
||||
#include <stdio.h> |
||||
#include <sys/queue.h> |
||||
|
||||
#define TWHEEL_VERSION 0x20160226 |
||||
|
||||
typedef uint64_t twheel_t; |
||||
#define TWHEEL_C(n) UINT64_C(n) |
||||
#define TWHEEL_mHZ TWHEEL_C(1000) |
||||
#define TWHEEL_MAX ((TWHEEL_C(1) << 24) - 1) |
||||
|
||||
enum twheel_flags { |
||||
TWHEEL_INT = 0x01, |
||||
TWHEEL_ABS = 0x02, |
||||
}; |
||||
|
||||
#define TWHEEL_INITIALIZER(flags) { (flags) } |
||||
|
||||
struct twheel { |
||||
int flags; |
||||
twheel_t expires; |
||||
struct twheel_list *pending; |
||||
TAILQ_ENTRY(twheel) tqe; |
||||
twheel_t interval; |
||||
}; |
||||
|
||||
TAILQ_HEAD(twheel_list, twheel); |
||||
|
||||
struct twheels { |
||||
struct twheel_list wheel[4][64], expired; |
||||
uint64_t pending[4]; |
||||
twheel_t curtime; |
||||
twheel_t hertz; |
||||
}; |
||||
|
||||
struct twheel *twheel_init(struct twheel *, int flags); |
||||
bool twheel_pending(struct twheel *); |
||||
|
||||
struct twheels *twheels_open(twheel_t hz); |
||||
void twheels_close(struct twheels *); |
||||
twheel_t twheels_hz(struct twheels *); |
||||
void twheels_update(struct twheels *, twheel_t curtime); |
||||
twheel_t twheels_timeout(struct twheels *); |
||||
void twheels_add(struct twheels *, struct twheel *, twheel_t timeout); |
||||
void twheels_del(struct twheels *, struct twheel *); |
||||
struct twheel *twheels_get(struct twheels *); |
||||
bool twheels_pending(struct twheels *); |
||||
bool twheels_expired(struct twheels *); |
||||
|
||||
#endif |
||||
@ -0,0 +1,35 @@
|
||||
// twheel_bitops.c — ctz/clz for timing wheel (included by twheel.c)
|
||||
|
||||
#include <stdint.h> |
||||
|
||||
#if defined(__GNUC__) && !defined(TWHEEL_DISABLE_GNUC_BITOPS) |
||||
#define ctz64(n) __builtin_ctzll(n) |
||||
#define clz64(n) __builtin_clzll(n) |
||||
#if LONG_BITS == 32 |
||||
#define ctz32(n) __builtin_ctzl(n) |
||||
#define clz32(n) __builtin_clzl(n) |
||||
#else |
||||
#define ctz32(n) __builtin_ctz(n) |
||||
#define clz32(n) __builtin_clz(n) |
||||
#endif |
||||
#elif defined(_MSC_VER) && !defined(TWHEEL_DISABLE_MSVC_BITOPS) |
||||
#include <intrin.h> |
||||
static __inline int ctz32(unsigned long val) { DWORD z=0; _BitScanForward(&z,val); return z; } |
||||
static __inline int clz32(unsigned long val) { DWORD z=0; _BitScanReverse(&z,val); return z; } |
||||
#ifdef _WIN64 |
||||
static __inline int ctz64(uint64_t val) { DWORD z=0; _BitScanForward64(&z,val); return z; } |
||||
static __inline int clz64(uint64_t val) { DWORD z=0; _BitScanReverse64(&z,val); return z; } |
||||
#else |
||||
static __inline int ctz64(uint64_t val) { uint32_t lo=(uint32_t)val,hi=(uint32_t)(val>>32); return lo?ctz32(lo):32+ctz32(hi); } |
||||
static __inline int clz64(uint64_t val) { uint32_t lo=(uint32_t)val,hi=(uint32_t)(val>>32); return hi?clz32(hi):32+clz32(lo); } |
||||
#endif |
||||
#else |
||||
#define process_(one, cz_bits, bits) if (x < ( one << (cz_bits - bits))) { rv += bits; x <<= bits; } |
||||
static inline int clz64(uint64_t x) { int rv=0; process_(UINT64_C(1),64,32); process_(UINT64_C(1),64,16); process_(UINT64_C(1),64,8); process_(UINT64_C(1),64,4); process_(UINT64_C(1),64,2); process_(UINT64_C(1),64,1); return rv; } |
||||
static inline int clz32(uint32_t x) { int rv=0; process_(UINT32_C(1),32,16); process_(UINT32_C(1),32,8); process_(UINT32_C(1),32,4); process_(UINT32_C(1),32,2); process_(UINT32_C(1),32,1); return rv; } |
||||
#undef process_ |
||||
#define process_(one, bits) if ((x & ((one << (bits))-1)) == 0) { rv += bits; x >>= bits; } |
||||
static inline int ctz64(uint64_t x) { int rv=0; process_(UINT64_C(1),32); process_(UINT64_C(1),16); process_(UINT64_C(1),8); process_(UINT64_C(1),4); process_(UINT64_C(1),2); process_(UINT64_C(1),1); return rv; } |
||||
static inline int ctz32(uint32_t x) { int rv=0; process_(UINT32_C(1),16); process_(UINT32_C(1),8); process_(UINT32_C(1),4); process_(UINT32_C(1),2); process_(UINT32_C(1),1); return rv; } |
||||
#undef process_ |
||||
#endif |
||||
Loading…
Reference in new issue