Coverage Report

Created: 2025-08-26 06:33

/src/h2o/lib/common/timerwheel.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright (c) 2017 Fastly, Inc.
3
 *
4
 * Permission is hereby granted, free of charge, to any person obtaining a copy
5
 * of this software and associated documentation files (the "Software"), to
6
 * deal in the Software without restriction, including without limitation the
7
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
8
 * sell copies of the Software, and to permit persons to whom the Software is
9
 * furnished to do so, subject to the following conditions:
10
 *
11
 * The above copyright notice and this permission notice shall be included in
12
 * all copies or substantial portions of the Software.
13
 *
14
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
20
 * IN THE SOFTWARE.
21
 */
22
#include <string.h>
23
#include <stdio.h>
24
#include <inttypes.h>
25
#include "h2o/memory.h"
26
#include "h2o/timerwheel.h"
27
28
3.87M
#define H2O_TIMERWHEEL_SLOTS_MASK (H2O_TIMERWHEEL_SLOTS_PER_WHEEL - 1)
29
30
#ifndef H2O_TIMER_VALIDATE
31
22.8k
#define H2O_TIMER_VALIDATE 0
32
#endif
33
34
#define REPORT_CORRUPT_TIMER(ctx, e, fmt, ...)                                                                                     \
35
0
    do {                                                                                                                           \
36
0
        h2o_timerwheel_entry_t *_e = (e);                                                                                          \
37
0
        h2o_error_printf("%s:%d:last_run=%" PRIu64 fmt ", timer(%p)={expire_at=%" PRIu64 ", cb=%p}\n", __FUNCTION__, __LINE__,     \
38
0
                         (ctx)->last_run, __VA_ARGS__, _e, _e->expire_at, _e->cb);                                                 \
39
0
    } while (0)
40
41
#define ABORT_CORRUPT_TIMER(ctx, t, fmt, ...)                                                                                      \
42
0
    do {                                                                                                                           \
43
0
        REPORT_CORRUPT_TIMER(ctx, t, fmt, __VA_ARGS__);                                                                            \
44
0
        h2o_fatal("timerwheel");                                                                                                   \
45
0
    } while (0)
46
47
struct st_h2o_timerwheel_t {
48
    /**
49
     * the last time h2o_timer_run_wheel was called
50
     */
51
    uint64_t last_run;
52
    /**
53
     * maximum ticks that can be retained safely in the structure. Objects that need to be retained longer will be re-registered at
54
     * the highest wheel.
55
     */
56
    uint64_t max_ticks;
57
    /**
58
     * number of wheels and the wheel
59
     */
60
    size_t num_wheels;
61
    h2o_linklist_t wheels[][H2O_TIMERWHEEL_SLOTS_PER_WHEEL];
62
};
63
64
void h2o_timerwheel_dump(h2o_timerwheel_t *ctx)
65
0
{
66
0
    size_t wheel, slot;
67
68
0
    h2o_error_printf("%s(%p):\n", __FUNCTION__, ctx);
69
0
    for (wheel = 0; wheel < ctx->num_wheels; wheel++) {
70
0
        for (slot = 0; slot < H2O_TIMERWHEEL_SLOTS_PER_WHEEL; slot++) {
71
0
            h2o_linklist_t *anchor = &ctx->wheels[wheel][slot], *l;
72
0
            for (l = anchor->next; l != anchor; l = l->next) {
73
0
                h2o_timerwheel_entry_t *e = H2O_STRUCT_FROM_MEMBER(h2o_timerwheel_entry_t, _link, l);
74
0
                h2o_error_printf("  - {wheel: %zu, slot: %zu, expires:%" PRIu64 ", self: %p, cb:%p}\n", wheel, slot, e->expire_at,
75
0
                                 e, e->cb);
76
0
            }
77
0
        }
78
0
    }
79
0
}
80
81
static size_t timer_wheel(size_t num_wheels, uint64_t delta)
82
22.8k
{
83
22.8k
    H2O_BUILD_ASSERT(sizeof(unsigned long long) == 8);
84
85
22.8k
    if (delta == 0)
86
1.38k
        return 0;
87
21.4k
    return (63 - __builtin_clzll(delta)) / H2O_TIMERWHEEL_BITS_PER_WHEEL;
88
22.8k
}
89
90
/* calculate slot number based on the absolute expiration time */
91
static size_t timer_slot(size_t wheel, uint64_t expire)
92
3.87M
{
93
3.87M
    return H2O_TIMERWHEEL_SLOTS_MASK & (expire >> (wheel * H2O_TIMERWHEEL_BITS_PER_WHEEL));
94
3.87M
}
95
96
/**
97
 * returned at_max is inclusive
98
 */
99
static void calc_expire_for_slot(size_t num_wheels, uint64_t last_run, size_t wheel, size_t slot, uint64_t *at_min,
100
                                 uint64_t *at_max)
101
0
{
102
0
#define SPAN(i) ((uint64_t)1 << (H2O_TIMERWHEEL_BITS_PER_WHEEL * (i))) /* returns the span of time for given wheel index */
103
104
0
    int adj_at_min = 0;
105
106
0
    *at_min = (last_run & ~(SPAN(wheel + 1) - 1)) + slot * SPAN(wheel);
107
108
0
    if (wheel == 0) {
109
0
        if (*at_min < last_run)
110
0
            adj_at_min = 1;
111
0
    } else {
112
0
        if (*at_min <= last_run)
113
0
            adj_at_min = 1;
114
0
    }
115
0
    if (adj_at_min)
116
0
        *at_min += SPAN(wheel + 1);
117
118
0
    if (wheel == num_wheels - 1) {
119
0
        *at_max = UINT64_MAX;
120
0
    } else {
121
0
        *at_max = *at_min + SPAN(wheel) - 1;
122
0
    }
123
124
0
#undef SPAN
125
0
}
126
127
static int validate_slot(h2o_timerwheel_t *ctx, size_t wheel, size_t slot)
128
0
{
129
0
    h2o_linklist_t *anchor = &ctx->wheels[wheel][slot], *link;
130
0
    uint64_t at_min, at_max;
131
0
    int success = 1;
132
133
0
    calc_expire_for_slot(ctx->num_wheels, ctx->last_run, wheel, slot, &at_min, &at_max);
134
135
0
    for (link = anchor->next; link != anchor; link = link->next) {
136
0
        h2o_timerwheel_entry_t *e = H2O_STRUCT_FROM_MEMBER(h2o_timerwheel_entry_t, _link, link);
137
0
        if (!(at_min <= e->expire_at && e->expire_at <= at_max)) {
138
0
            REPORT_CORRUPT_TIMER(ctx, e, ", wheel=%zu, slot=%zu, expected_range=[%" PRIu64 ",%" PRIu64 "]", wheel, slot, at_min,
139
0
                                 at_max);
140
0
            success = 0;
141
0
        }
142
0
    }
143
144
0
    return success;
145
0
}
146
147
int h2o_timerwheel_validate(h2o_timerwheel_t *ctx)
148
0
{
149
0
    size_t wheel, slot;
150
0
    int success = 1;
151
152
0
    for (wheel = 0; wheel < ctx->num_wheels; ++wheel)
153
0
        for (slot = 0; slot < H2O_TIMERWHEEL_SLOTS_PER_WHEEL; ++slot)
154
0
            if (!validate_slot(ctx, wheel, slot))
155
0
                success = 0;
156
157
0
    return success;
158
0
}
159
160
uint64_t h2o_timerwheel_get_wake_at(h2o_timerwheel_t *ctx)
161
1.88M
{
162
1.88M
    size_t wheel, slot;
163
1.88M
    uint64_t at = ctx->last_run;
164
165
1.88M
    for (wheel = 0; wheel < ctx->num_wheels; ++wheel) {
166
1.88M
        uint64_t at_incr = (uint64_t)1 << (wheel * H2O_TIMERWHEEL_BITS_PER_WHEEL);
167
1.88M
        size_t slot_base = timer_slot(wheel, at);
168
        /* check current wheel from slot_base */
169
3.76M
        for (slot = slot_base; slot < H2O_TIMERWHEEL_SLOTS_PER_WHEEL; ++slot) {
170
3.70M
            if (!h2o_linklist_is_empty(&ctx->wheels[wheel][slot]))
171
1.82M
                goto Found;
172
1.88M
            at += at_incr;
173
1.88M
        }
174
61.6k
        while (1) {
175
            /* handle carry */
176
61.6k
            if (wheel + 1 < ctx->num_wheels) {
177
61.6k
                size_t wi;
178
61.6k
                for (wi = wheel + 1; wi < ctx->num_wheels; ++wi) {
179
61.6k
                    size_t si = timer_slot(wi, at);
180
61.6k
                    if (!h2o_linklist_is_empty(&ctx->wheels[wi][si]))
181
1
                        goto Found;
182
61.6k
                    if (si != 0)
183
61.6k
                        break;
184
61.6k
                }
185
61.6k
            }
186
            /* check current wheel from 0 to slot_base */
187
61.6k
            if (slot_base == 0)
188
0
                break;
189
61.6k
            for (slot = 0; slot < slot_base; ++slot) {
190
61.6k
                if (!h2o_linklist_is_empty(&ctx->wheels[wheel][slot]))
191
61.6k
                    goto Found;
192
10
                at += at_incr;
193
10
            }
194
0
            at += at_incr * (H2O_TIMERWHEEL_SLOTS_PER_WHEEL - slot_base);
195
0
            slot_base = 0;
196
0
        }
197
61.6k
    }
198
199
    /* not found */
200
0
    return UINT64_MAX;
201
1.88M
Found:
202
1.88M
    return at;
203
1.88M
}
204
205
static void link_timer(h2o_timerwheel_t *ctx, h2o_timerwheel_entry_t *entry)
206
22.8k
{
207
22.8k
    size_t wheel, slot;
208
22.8k
    uint64_t wheel_abs = entry->expire_at;
209
210
22.8k
    if (wheel_abs > ctx->last_run + ctx->max_ticks)
211
0
        wheel_abs = ctx->last_run + ctx->max_ticks;
212
213
22.8k
    wheel = timer_wheel(ctx->num_wheels, wheel_abs - ctx->last_run);
214
22.8k
    slot = timer_slot(wheel, wheel_abs);
215
216
22.8k
    if (H2O_TIMER_VALIDATE) {
217
0
        uint64_t at_min, at_max;
218
0
        calc_expire_for_slot(ctx->num_wheels, ctx->last_run, wheel, slot, &at_min, &at_max);
219
0
        if (!(at_min <= entry->expire_at && entry->expire_at <= at_max))
220
0
            ABORT_CORRUPT_TIMER(ctx, entry, ", wheel=%zu, slot=%zu, at_min=%" PRIu64 ", at_max=%" PRIu64, wheel, slot, at_min,
221
0
                                at_max);
222
0
    }
223
224
22.8k
    h2o_linklist_insert(&ctx->wheels[wheel][slot], &entry->_link);
225
22.8k
}
226
227
/* timer wheel APIs */
228
229
/**
230
 * initializes a timerwheel
231
 */
232
h2o_timerwheel_t *h2o_timerwheel_create(size_t num_wheels, uint64_t now)
233
1
{
234
1
    h2o_timerwheel_t *ctx = h2o_mem_alloc(offsetof(h2o_timerwheel_t, wheels) + sizeof(ctx->wheels[0]) * num_wheels);
235
1
    size_t i, j;
236
237
1
    ctx->last_run = now;
238
    /* max_ticks cannot be so large that the entry will be linked once more to the same slot, see the assert in `cascade` */
239
1
    ctx->max_ticks = ((uint64_t)1 << (H2O_TIMERWHEEL_BITS_PER_WHEEL * (num_wheels - 1))) * (H2O_TIMERWHEEL_SLOTS_PER_WHEEL - 1);
240
1
    ctx->num_wheels = num_wheels;
241
4
    for (i = 0; i < ctx->num_wheels; i++)
242
99
        for (j = 0; j < H2O_TIMERWHEEL_SLOTS_PER_WHEEL; j++)
243
96
            h2o_linklist_init_anchor(&ctx->wheels[i][j]);
244
245
1
    return ctx;
246
1
}
247
248
void h2o_timerwheel_destroy(h2o_timerwheel_t *ctx)
249
0
{
250
0
    size_t i, j;
251
252
0
    for (i = 0; i < ctx->num_wheels; ++i) {
253
0
        for (j = 0; j < H2O_TIMERWHEEL_SLOTS_PER_WHEEL; ++j) {
254
0
            while (!h2o_linklist_is_empty(&ctx->wheels[i][j])) {
255
0
                h2o_timerwheel_entry_t *entry = H2O_STRUCT_FROM_MEMBER(h2o_timerwheel_entry_t, _link, ctx->wheels[i][j].next);
256
0
                h2o_timerwheel_unlink(entry);
257
0
            }
258
0
        }
259
0
    }
260
261
0
    free(ctx);
262
0
}
263
264
/**
265
 * cascading happens when the lower wheel wraps around and ticks the next
266
 * higher wheel
267
 */
268
static void cascade_one(h2o_timerwheel_t *ctx, size_t wheel, size_t slot)
269
675
{
270
675
    assert(wheel > 0);
271
272
675
    h2o_linklist_t *s = &ctx->wheels[wheel][slot];
273
274
677
    while (!h2o_linklist_is_empty(s)) {
275
2
        h2o_timerwheel_entry_t *entry = H2O_STRUCT_FROM_MEMBER(h2o_timerwheel_entry_t, _link, s->next);
276
2
        if (entry->expire_at < ctx->last_run)
277
0
            ABORT_CORRUPT_TIMER(ctx, entry, ", wheel=%zu, slot=%zu", wheel, slot);
278
2
        h2o_linklist_unlink(&entry->_link);
279
2
        link_timer(ctx, entry);
280
2
        assert(&entry->_link != s->prev); /* detect the entry reassigned to the same slot */
281
2
    }
282
675
}
283
284
static int cascade_all(h2o_timerwheel_t *ctx, size_t wheel)
285
655
{
286
655
    int cascaded = 0;
287
288
675
    for (; wheel < ctx->num_wheels; ++wheel) {
289
675
        size_t slot = timer_slot(wheel, ctx->last_run);
290
675
        if (!h2o_linklist_is_empty(&ctx->wheels[wheel][slot]))
291
1
            cascaded = 1;
292
675
        cascade_one(ctx, wheel, slot);
293
675
        if (slot != 0)
294
655
            break;
295
675
    }
296
297
655
    return cascaded;
298
655
}
299
300
void h2o_timerwheel_get_expired(h2o_timerwheel_t *ctx, uint64_t now, h2o_linklist_t *expired)
301
1.90M
{
302
1.90M
    size_t wheel = 0, slot, slot_start;
303
304
    /* time might rewind if the clock is reset */
305
1.90M
    if (now < ctx->last_run) {
306
0
        h2o_error_printf("%s:detected rewind; last_run=%" PRIu64 ", now=%" PRIu64 "\n", __FUNCTION__, ctx->last_run, now);
307
0
        return;
308
0
    }
309
310
1.90M
Redo:
311
    /* collect from the first slot */
312
1.90M
    slot_start = timer_slot(wheel, ctx->last_run);
313
1.92M
    for (slot = slot_start; slot < H2O_TIMERWHEEL_SLOTS_PER_WHEEL; ++slot) {
314
1.92M
        if (wheel == 0) {
315
1.92M
            h2o_linklist_insert_list(expired, &ctx->wheels[wheel][slot]);
316
1.92M
            if (ctx->last_run == now)
317
1.90M
                goto Exit;
318
20.9k
            ++ctx->last_run;
319
20.9k
        } else {
320
0
            if (!h2o_linklist_is_empty(&ctx->wheels[wheel][slot])) {
321
0
                cascade_one(ctx, wheel, slot);
322
0
                assert(h2o_linklist_is_empty(&ctx->wheels[wheel][slot]));
323
0
                wheel = 0;
324
0
                goto Redo;
325
0
            }
326
0
            ctx->last_run += 1 << (wheel * H2O_TIMERWHEEL_BITS_PER_WHEEL);
327
0
            if (ctx->last_run > now) {
328
0
                ctx->last_run = now;
329
0
                goto Exit;
330
0
            }
331
0
        }
332
1.92M
    }
333
    /* carry */
334
655
    if (cascade_all(ctx, wheel != 0 ? wheel : 1)) {
335
1
        wheel = 0;
336
1
        goto Redo;
337
1
    }
338
654
    if (slot_start != 0 || ++wheel < ctx->num_wheels)
339
654
        goto Redo;
340
    /* all the wheels were empty, and they all belonged to the past */
341
0
    if (ctx->last_run < now)
342
0
        ctx->last_run = now;
343
344
1.90M
Exit:
345
1.90M
    assert(ctx->last_run == now);
346
1.90M
}
347
348
size_t h2o_timerwheel_run(h2o_timerwheel_t *ctx, uint64_t now)
349
0
{
350
0
    h2o_linklist_t expired;
351
0
    size_t count = 0;
352
353
0
    h2o_linklist_init_anchor(&expired);
354
0
    h2o_timerwheel_get_expired(ctx, now, &expired);
355
0
    while (!h2o_linklist_is_empty(&expired)) {
356
0
        h2o_timerwheel_entry_t *entry = H2O_STRUCT_FROM_MEMBER(h2o_timerwheel_entry_t, _link, expired.next);
357
0
        h2o_linklist_unlink(&entry->_link);
358
0
        entry->cb(entry);
359
0
        ++count;
360
0
    }
361
362
0
    return count;
363
0
}
364
365
void h2o_timerwheel_link_abs(h2o_timerwheel_t *ctx, h2o_timerwheel_entry_t *entry, uint64_t at)
366
22.8k
{
367
22.8k
    entry->expire_at = at < ctx->last_run ? ctx->last_run : at;
368
22.8k
    link_timer(ctx, entry);
369
22.8k
}