Coverage Report

Created: 2025-11-29 06:45

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/h2o/lib/http2/scheduler.c
Line
Count
Source
1
/*
2
 * Copyright (c) 2015 DeNA Co., Ltd.
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 "h2o.h"
23
#include "h2o/http2_internal.h"
24
#include "h2o/http2_scheduler.h"
25
26
struct st_h2o_http2_scheduler_queue_t {
27
    uint64_t bits;
28
    size_t offset;
29
    h2o_linklist_t anchors[64];
30
    h2o_linklist_t anchor257;
31
};
32
33
static void queue_init(h2o_http2_scheduler_queue_t *queue)
34
4.69k
{
35
4.69k
    size_t i;
36
4.69k
    queue->bits = 0;
37
4.69k
    queue->offset = 0;
38
304k
    for (i = 0; i != sizeof(queue->anchors) / sizeof(queue->anchors[0]); ++i)
39
300k
        h2o_linklist_init_anchor(queue->anchors + i);
40
4.69k
    h2o_linklist_init_anchor(&queue->anchor257);
41
4.69k
}
42
43
static int queue_is_empty(h2o_http2_scheduler_queue_t *queue)
44
12.4k
{
45
12.4k
    return queue->bits == 0 && h2o_linklist_is_empty(&queue->anchor257);
46
12.4k
}
47
48
static void queue_set(h2o_http2_scheduler_queue_t *queue, h2o_http2_scheduler_queue_node_t *node, uint16_t weight)
49
37.2k
{
50
    /* holds 257 entries of offsets (multiplied by 65536) where nodes with weights between 1..257 should go into
51
     * each entry (except for weight=256) is calculated as: round(N / weight), where N is adjusted so that the
52
     * value would become 63*65536 for weight=0.
53
     * weight=257 is used internally to send data before any of the streams being pulled, and therefore has the offset set to zero.
54
     */
55
37.2k
    static const unsigned OFFSET_TABLE[] = {
56
37.2k
        4128768, 2064384, 1376256, 1032192, 825754, 688128, 589824, 516096, 458752, 412877, 375343, 344064, 317598, 294912, 275251,
57
37.2k
        258048,  242869,  229376,  217304,  206438, 196608, 187671, 179512, 172032, 165151, 158799, 152917, 147456, 142371, 137626,
58
37.2k
        133186,  129024,  125114,  121434,  117965, 114688, 111588, 108652, 105866, 103219, 100702, 98304,  96018,  93836,  91750,
59
37.2k
        89756,   87846,   86016,   84261,   82575,  80956,  79399,  77901,  76459,  75069,  73728,  72435,  71186,  69979,  68813,
60
37.2k
        67685,   66593,   65536,   64512,   63520,  62557,  61623,  60717,  59837,  58982,  58152,  57344,  56558,  55794,  55050,
61
37.2k
        54326,   53620,   52933,   52263,   51610,  50972,  50351,  49744,  49152,  48574,  48009,  47457,  46918,  46391,  45875,
62
37.2k
        45371,   44878,   44395,   43923,   43461,  43008,  42565,  42130,  41705,  41288,  40879,  40478,  40085,  39700,  39322,
63
37.2k
        38951,   38587,   38229,   37879,   37534,  37196,  36864,  36538,  36217,  35902,  35593,  35289,  34990,  34696,  34406,
64
37.2k
        34122,   33842,   33567,   33297,   33030,  32768,  32510,  32256,  32006,  31760,  31517,  31279,  31043,  30812,  30583,
65
37.2k
        30359,   30137,   29919,   29703,   29491,  29282,  29076,  28873,  28672,  28474,  28279,  28087,  27897,  27710,  27525,
66
37.2k
        27343,   27163,   26985,   26810,   26637,  26466,  26298,  26131,  25967,  25805,  25645,  25486,  25330,  25175,  25023,
67
37.2k
        24872,   24723,   24576,   24431,   24287,  24145,  24004,  23866,  23729,  23593,  23459,  23326,  23195,  23066,  22938,
68
37.2k
        22811,   22686,   22562,   22439,   22318,  22198,  22079,  21962,  21845,  21730,  21617,  21504,  21393,  21282,  21173,
69
37.2k
        21065,   20958,   20852,   20748,   20644,  20541,  20439,  20339,  20239,  20140,  20043,  19946,  19850,  19755,  19661,
70
37.2k
        19568,   19475,   19384,   19293,   19204,  19115,  19027,  18939,  18853,  18767,  18682,  18598,  18515,  18432,  18350,
71
37.2k
        18269,   18188,   18109,   18030,   17951,  17873,  17796,  17720,  17644,  17569,  17495,  17421,  17348,  17275,  17203,
72
37.2k
        17132,   17061,   16991,   16921,   16852,  16784,  16716,  16648,  16581,  16515,  16449,  16384,  16319,  16255,  16191,
73
37.2k
        16128,   0};
74
75
37.2k
    assert(!h2o_linklist_is_linked(&node->_link));
76
77
37.2k
    if (weight > 256) {
78
79
0
        h2o_linklist_insert(&queue->anchor257, &node->_link);
80
81
37.2k
    } else {
82
83
37.2k
        assert(1 <= weight);
84
85
37.2k
        size_t offset = OFFSET_TABLE[weight - 1] + node->_deficit;
86
37.2k
        node->_deficit = offset % 65536;
87
37.2k
        offset = offset / 65536;
88
89
37.2k
        queue->bits |= 1ULL << (sizeof(queue->bits) * 8 - 1 - offset);
90
37.2k
        h2o_linklist_insert(queue->anchors + (queue->offset + offset) % (sizeof(queue->anchors) / sizeof(queue->anchors[0])),
91
37.2k
                            &node->_link);
92
37.2k
    }
93
37.2k
}
94
95
static void queue_unset(h2o_http2_scheduler_queue_node_t *node)
96
26.7k
{
97
26.7k
    assert(h2o_linklist_is_linked(&node->_link));
98
26.7k
    h2o_linklist_unlink(&node->_link);
99
26.7k
}
100
101
static h2o_http2_scheduler_queue_node_t *queue_pop(h2o_http2_scheduler_queue_t *queue)
102
10.9k
{
103
10.9k
    if (!h2o_linklist_is_empty(&queue->anchor257)) {
104
0
        h2o_http2_scheduler_queue_node_t *node =
105
0
            H2O_STRUCT_FROM_MEMBER(h2o_http2_scheduler_queue_node_t, _link, queue->anchor257.next);
106
0
        h2o_linklist_unlink(&node->_link);
107
0
        return node;
108
0
    }
109
110
12.1k
    while (queue->bits != 0) {
111
11.7k
        int zeroes = __builtin_clzll(queue->bits);
112
11.7k
        queue->bits <<= zeroes;
113
11.7k
        queue->offset = (queue->offset + zeroes) % (sizeof(queue->anchors) / sizeof(queue->anchors[0]));
114
11.7k
        if (!h2o_linklist_is_empty(queue->anchors + queue->offset)) {
115
10.5k
            h2o_http2_scheduler_queue_node_t *node =
116
10.5k
                H2O_STRUCT_FROM_MEMBER(h2o_http2_scheduler_queue_node_t, _link, queue->anchors[queue->offset].next);
117
10.5k
            h2o_linklist_unlink(&node->_link);
118
10.5k
            if (h2o_linklist_is_empty(queue->anchors + queue->offset))
119
7.51k
                queue->bits &= (1ULL << (sizeof(queue->bits) * 8 - 1)) - 1;
120
10.5k
            return node;
121
10.5k
        }
122
1.21k
        queue->bits &= (1ULL << (sizeof(queue->bits) * 8 - 1)) - 1;
123
1.21k
    }
124
438
    return NULL;
125
10.9k
}
126
127
static void init_node(h2o_http2_scheduler_node_t *node, h2o_http2_scheduler_node_t *parent)
128
105k
{
129
105k
    *node = (h2o_http2_scheduler_node_t){parent};
130
105k
    h2o_linklist_init_anchor(&node->_all_refs);
131
105k
}
132
133
static h2o_http2_scheduler_queue_t *get_queue(h2o_http2_scheduler_node_t *node)
134
32.6k
{
135
32.6k
    if (node->_queue == NULL) {
136
4.69k
        node->_queue = h2o_mem_alloc(sizeof(*node->_queue));
137
4.69k
        queue_init(node->_queue);
138
4.69k
    }
139
32.6k
    return node->_queue;
140
32.6k
}
141
142
static void incr_active_cnt(h2o_http2_scheduler_node_t *node)
143
41.1k
{
144
41.1k
    h2o_http2_scheduler_openref_t *ref;
145
146
    /* do nothing if node is the root */
147
41.1k
    if (node->_parent == NULL)
148
12.2k
        return;
149
150
28.8k
    ref = (h2o_http2_scheduler_openref_t *)node;
151
28.8k
    if (++ref->_active_cnt != 1)
152
4.21k
        return;
153
    /* just changed to active */
154
24.6k
    queue_set(get_queue(ref->node._parent), &ref->_queue_node, ref->weight);
155
    /* delegate the change towards root */
156
24.6k
    incr_active_cnt(ref->node._parent);
157
24.6k
}
158
159
static void decr_active_cnt(h2o_http2_scheduler_node_t *node)
160
35.1k
{
161
35.1k
    h2o_http2_scheduler_openref_t *ref;
162
163
    /* do nothing if node is the root */
164
35.1k
    if (node->_parent == NULL)
165
12.2k
        return;
166
167
22.9k
    ref = (h2o_http2_scheduler_openref_t *)node;
168
22.9k
    if (--ref->_active_cnt != 0)
169
4.11k
        return;
170
    /* just changed to inactive */
171
18.8k
    queue_unset(&ref->_queue_node);
172
    /* delegate the change towards root */
173
18.8k
    decr_active_cnt(ref->node._parent);
174
18.8k
}
175
176
static void convert_to_exclusive(h2o_http2_scheduler_node_t *parent, h2o_http2_scheduler_openref_t *added)
177
13.1k
{
178
25.0k
    while (!h2o_linklist_is_empty(&parent->_all_refs)) {
179
25.0k
        h2o_http2_scheduler_openref_t *child_ref =
180
25.0k
            H2O_STRUCT_FROM_MEMBER(h2o_http2_scheduler_openref_t, _all_link, parent->_all_refs.next);
181
25.0k
        if (child_ref == added) {
182
            /* precond: the added node should exist as the last item within parent */
183
13.1k
            assert(parent->_all_refs.prev == &added->_all_link);
184
13.1k
            break;
185
13.1k
        }
186
11.9k
        h2o_http2_scheduler_rebind(child_ref, &added->node, h2o_http2_scheduler_get_weight(child_ref), 0);
187
11.9k
    }
188
13.1k
}
189
190
void h2o_http2_scheduler_open(h2o_http2_scheduler_openref_t *ref, h2o_http2_scheduler_node_t *parent, uint16_t weight,
191
                              int exclusive)
192
47.2k
{
193
47.2k
    init_node(&ref->node, parent);
194
47.2k
    ref->weight = weight;
195
47.2k
    ref->_all_link = (h2o_linklist_t){NULL};
196
47.2k
    ref->_active_cnt = 0;
197
47.2k
    ref->_self_is_active = 0;
198
47.2k
    ref->_queue_node = (h2o_http2_scheduler_queue_node_t){{NULL}};
199
47.2k
    ref->_is_relocated = 0;
200
201
47.2k
    h2o_linklist_insert(&parent->_all_refs, &ref->_all_link);
202
203
47.2k
    if (exclusive)
204
5.21k
        convert_to_exclusive(parent, ref);
205
47.2k
}
206
207
void h2o_http2_scheduler_close(h2o_http2_scheduler_openref_t *ref)
208
47.2k
{
209
47.2k
    assert(h2o_http2_scheduler_is_open(ref));
210
211
    /* move dependents to parent */
212
47.2k
    if (!h2o_linklist_is_empty(&ref->node._all_refs)) {
213
        /* proportionally distribute the weight to the children (draft-16 5.3.4) */
214
4.43k
        uint32_t total_weight = 0, factor;
215
4.43k
        h2o_linklist_t *link;
216
12.9k
        for (link = ref->node._all_refs.next; link != &ref->node._all_refs; link = link->next) {
217
8.48k
            h2o_http2_scheduler_openref_t *child_ref = H2O_STRUCT_FROM_MEMBER(h2o_http2_scheduler_openref_t, _all_link, link);
218
8.48k
            total_weight += child_ref->weight;
219
8.48k
        }
220
4.43k
        assert(total_weight != 0);
221
4.43k
        factor = ((uint32_t)ref->weight * 65536 + total_weight / 2) / total_weight;
222
8.48k
        do {
223
8.48k
            h2o_http2_scheduler_openref_t *child_ref =
224
8.48k
                H2O_STRUCT_FROM_MEMBER(h2o_http2_scheduler_openref_t, _all_link, ref->node._all_refs.next);
225
8.48k
            uint16_t weight = (child_ref->weight * factor / 32768 + 1) / 2;
226
8.48k
            if (weight < 1)
227
1.23k
                weight = 1;
228
7.25k
            else if (weight > 256)
229
0
                weight = 256;
230
8.48k
            h2o_http2_scheduler_rebind(child_ref, ref->node._parent, weight, 0);
231
8.48k
        } while (!h2o_linklist_is_empty(&ref->node._all_refs));
232
4.43k
    }
233
234
47.2k
    free(ref->node._queue);
235
47.2k
    ref->node._queue = NULL;
236
237
    /* detach self */
238
47.2k
    h2o_linklist_unlink(&ref->_all_link);
239
47.2k
    if (ref->_self_is_active) {
240
0
        assert(ref->_active_cnt == 1);
241
0
        queue_unset(&ref->_queue_node);
242
0
        decr_active_cnt(ref->node._parent);
243
47.2k
    } else {
244
47.2k
        assert(ref->_active_cnt == 0);
245
47.2k
    }
246
47.2k
}
247
248
void h2o_http2_scheduler_relocate(h2o_http2_scheduler_openref_t *dst, h2o_http2_scheduler_openref_t *src)
249
47.2k
{
250
47.2k
    init_node(&dst->node, src->node._parent);
251
47.2k
    dst->weight = src->weight;
252
47.2k
    dst->_all_link = (h2o_linklist_t){NULL};
253
47.2k
    dst->_active_cnt = src->_active_cnt;
254
47.2k
    dst->_self_is_active = src->_self_is_active;
255
47.2k
    dst->_queue_node._link = (h2o_linklist_t){NULL};
256
47.2k
    dst->_queue_node._deficit = src->_queue_node._deficit;
257
47.2k
    dst->_is_relocated = 1;
258
259
    /* update refs from descendants */
260
47.2k
    if (!h2o_linklist_is_empty(&src->node._all_refs)) {
261
5.04k
        h2o_linklist_t *link;
262
        /* update back reference */
263
13.1k
        for (link = src->node._all_refs.next; link != &src->node._all_refs; link = link->next) {
264
8.08k
            h2o_http2_scheduler_openref_t *child = H2O_STRUCT_FROM_MEMBER(h2o_http2_scheduler_openref_t, _all_link, link);
265
8.08k
            assert(child->node._parent == &src->node);
266
8.08k
            child->node._parent = &dst->node;
267
8.08k
        }
268
        /* attach the list to dst */
269
5.04k
        h2o_linklist_insert_list(&dst->node._all_refs, &src->node._all_refs);
270
        /* node._queue */
271
5.04k
        dst->node._queue = src->node._queue;
272
42.1k
    } else {
273
42.1k
        free(src->node._queue);
274
42.1k
    }
275
47.2k
    src->node._queue = NULL;
276
277
    /* swap all_link */
278
47.2k
    h2o_linklist_insert(&src->_all_link, &dst->_all_link);
279
47.2k
    h2o_linklist_unlink(&src->_all_link);
280
281
    /* swap _queue_node._link */
282
47.2k
    if (h2o_linklist_is_linked(&src->_queue_node._link)) {
283
4.03k
        h2o_linklist_insert(&src->_queue_node._link, &dst->_queue_node._link);
284
4.03k
        h2o_linklist_unlink(&src->_queue_node._link);
285
4.03k
    }
286
47.2k
}
287
288
static void do_rebind(h2o_http2_scheduler_openref_t *ref, h2o_http2_scheduler_node_t *new_parent, int exclusive)
289
31.1k
{
290
    /* rebind _all_link */
291
31.1k
    h2o_linklist_unlink(&ref->_all_link);
292
31.1k
    h2o_linklist_insert(&new_parent->_all_refs, &ref->_all_link);
293
    /* rebind to WRR (as well as adjust active_cnt) */
294
31.1k
    if (ref->_active_cnt != 0) {
295
7.94k
        queue_unset(&ref->_queue_node);
296
7.94k
        queue_set(get_queue(new_parent), &ref->_queue_node, ref->weight);
297
7.94k
        decr_active_cnt(ref->node._parent);
298
7.94k
        incr_active_cnt(new_parent);
299
7.94k
    }
300
    /* update the backlinks */
301
31.1k
    ref->node._parent = new_parent;
302
303
31.1k
    if (exclusive)
304
7.91k
        convert_to_exclusive(new_parent, ref);
305
31.1k
}
306
307
void h2o_http2_scheduler_rebind(h2o_http2_scheduler_openref_t *ref, h2o_http2_scheduler_node_t *new_parent, uint16_t weight,
308
                                int exclusive)
309
34.8k
{
310
34.8k
    assert(h2o_http2_scheduler_is_open(ref));
311
34.8k
    assert(&ref->node != new_parent);
312
34.8k
    assert(1 <= weight);
313
34.8k
    assert(weight <= 257);
314
315
    /* do nothing if there'd be no change at all */
316
34.8k
    if (ref->node._parent == new_parent && ref->weight == weight && !exclusive)
317
5.08k
        return;
318
319
29.8k
    ref->weight = weight;
320
321
    /* if new_parent is dependent to ref, make new_parent a sibling of ref before applying the final transition (see draft-16
322
       5.3.3) */
323
29.8k
    if (!h2o_linklist_is_empty(&ref->node._all_refs)) {
324
15.1k
        h2o_http2_scheduler_node_t *t;
325
41.5k
        for (t = new_parent; t->_parent != NULL; t = t->_parent) {
326
27.7k
            if (t->_parent == &ref->node) {
327
                /* quoting the spec: "The moved dependency retains its weight." */
328
1.32k
                h2o_http2_scheduler_openref_t *new_parent_ref = (void *)new_parent;
329
1.32k
                do_rebind(new_parent_ref, ref->node._parent, 0);
330
1.32k
                break;
331
1.32k
            }
332
27.7k
        }
333
15.1k
    }
334
335
29.8k
    do_rebind(ref, new_parent, exclusive);
336
29.8k
}
337
338
void h2o_http2_scheduler_init(h2o_http2_scheduler_node_t *root)
339
11.5k
{
340
11.5k
    init_node(root, NULL);
341
11.5k
}
342
343
void h2o_http2_scheduler_dispose(h2o_http2_scheduler_node_t *root)
344
11.1k
{
345
11.1k
    free(root->_queue);
346
11.1k
    root->_queue = NULL;
347
11.1k
}
348
349
void h2o_http2_scheduler_deactivate(h2o_http2_scheduler_openref_t *ref)
350
47.2k
{
351
47.2k
    if (!ref->_self_is_active)
352
44.7k
        return;
353
2.54k
    ref->_self_is_active = 0;
354
2.54k
    decr_active_cnt(&ref->node);
355
2.54k
}
356
357
void h2o_http2_scheduler_activate(h2o_http2_scheduler_openref_t *ref)
358
8.98k
{
359
8.98k
    if (ref->_self_is_active)
360
470
        return;
361
8.51k
    ref->_self_is_active = 1;
362
8.51k
    incr_active_cnt(&ref->node);
363
8.51k
}
364
365
static int proceed(h2o_http2_scheduler_node_t *node, h2o_http2_scheduler_run_cb cb, void *cb_arg)
366
6.40k
{
367
10.9k
Redo:
368
10.9k
    if (node->_queue == NULL)
369
0
        return 0;
370
371
10.9k
    h2o_http2_scheduler_queue_node_t *drr_node = queue_pop(node->_queue);
372
10.9k
    if (drr_node == NULL)
373
438
        return 0;
374
375
10.5k
    h2o_http2_scheduler_openref_t *ref = H2O_STRUCT_FROM_MEMBER(h2o_http2_scheduler_openref_t, _queue_node, drr_node);
376
377
10.5k
    if (!ref->_self_is_active) {
378
        /* run the children (manually-unrolled tail recursion) */
379
4.55k
        queue_set(node->_queue, &ref->_queue_node, ref->weight);
380
4.55k
        node = &ref->node;
381
4.55k
        goto Redo;
382
4.55k
    }
383
10.5k
    assert(ref->_active_cnt != 0);
384
385
    /* call the callbacks */
386
5.96k
    int still_is_active, bail_out = cb(ref, &still_is_active, cb_arg);
387
5.96k
    if (still_is_active) {
388
0
        queue_set(node->_queue, &ref->_queue_node, ref->weight);
389
5.96k
    } else {
390
5.96k
        ref->_self_is_active = 0;
391
5.96k
        if (--ref->_active_cnt != 0) {
392
104
            queue_set(node->_queue, &ref->_queue_node, ref->weight);
393
5.86k
        } else if (ref->node._parent != NULL) {
394
5.86k
            decr_active_cnt(ref->node._parent);
395
5.86k
        }
396
5.96k
    }
397
398
5.96k
    return bail_out;
399
5.96k
}
400
401
int h2o_http2_scheduler_run(h2o_http2_scheduler_node_t *root, h2o_http2_scheduler_run_cb cb, void *cb_arg)
402
7.92k
{
403
7.92k
    if (root->_queue != NULL) {
404
9.51k
        while (!queue_is_empty(root->_queue)) {
405
6.40k
            int bail_out = proceed(root, cb, cb_arg);
406
6.40k
            if (bail_out)
407
0
                return bail_out;
408
6.40k
        }
409
3.11k
    }
410
7.92k
    return 0;
411
7.92k
}
412
413
int h2o_http2_scheduler_is_active(h2o_http2_scheduler_node_t *root)
414
7.57k
{
415
7.57k
    return root->_queue != NULL && !queue_is_empty(root->_queue);
416
7.57k
}
417
418
h2o_http2_scheduler_node_t *h2o_http2_scheduler_find_parent_by_weight(h2o_http2_scheduler_node_t *root, uint16_t new_weight)
419
3.03k
{
420
3.03k
    h2o_http2_scheduler_node_t *node = root;
421
422
20.0k
    while (!h2o_linklist_is_empty(&node->_all_refs)) {
423
        /* This function (for now) assumes Chromium's dependency tree -- each stream shall have
424
         * no more than one child. Thus it only looks at the first child (`_all_refs.next`) */
425
18.5k
        h2o_http2_scheduler_openref_t *child_ref =
426
18.5k
            H2O_STRUCT_FROM_MEMBER(h2o_http2_scheduler_openref_t, _all_link, node->_all_refs.next);
427
18.5k
        if (!child_ref->_is_relocated && child_ref->weight < new_weight) {
428
            /* found a new parent */
429
1.59k
            break;
430
1.59k
        }
431
16.9k
        node = &child_ref->node;
432
16.9k
    }
433
3.03k
    return node;
434
3.03k
}