Coverage Report

Created: 2026-03-08 06:26

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/unit/src/nxt_timer.c
Line
Count
Source
1
2
/*
3
 * Copyright (C) Igor Sysoev
4
 * Copyright (C) NGINX, Inc.
5
 */
6
7
#include <nxt_main.h>
8
9
10
/*
11
 * Timer operations are batched in the changes array to improve instruction
12
 * and data cache locality of rbtree operations.
13
 *
14
 * nxt_timer_add() adds or modify a timer.
15
 *
16
 * nxt_timer_disable() disables a timer.
17
 *
18
 * nxt_timer_delete() deletes a timer.  It returns 1 if there are pending
19
 * changes in the changes array or 0 otherwise.
20
 */
21
22
static intptr_t nxt_timer_rbtree_compare(nxt_rbtree_node_t *node1,
23
    nxt_rbtree_node_t *node2);
24
static void nxt_timer_change(nxt_event_engine_t *engine, nxt_timer_t *timer,
25
    nxt_timer_operation_t change, nxt_msec_t time);
26
static void nxt_timer_changes_commit(nxt_event_engine_t *engine);
27
static void nxt_timer_handler(nxt_task_t *task, void *obj, void *data);
28
29
30
nxt_int_t
31
nxt_timers_init(nxt_timers_t *timers, nxt_uint_t mchanges)
32
0
{
33
0
    nxt_rbtree_init(&timers->tree, nxt_timer_rbtree_compare);
34
35
0
    if (mchanges > NXT_TIMER_MAX_CHANGES) {
36
0
        mchanges = NXT_TIMER_MAX_CHANGES;
37
0
    }
38
39
0
    timers->mchanges = mchanges;
40
41
0
    timers->changes = nxt_malloc(sizeof(nxt_timer_change_t) * mchanges);
42
43
0
    if (nxt_fast_path(timers->changes != NULL)) {
44
0
        return NXT_OK;
45
0
    }
46
47
0
    return NXT_ERROR;
48
0
}
49
50
51
static intptr_t
52
nxt_timer_rbtree_compare(nxt_rbtree_node_t *node1, nxt_rbtree_node_t *node2)
53
0
{
54
0
    nxt_timer_t  *timer1, *timer2;
55
56
0
    timer1 = (nxt_timer_t *) node1;
57
0
    timer2 = (nxt_timer_t *) node2;
58
59
    /*
60
     * Timer values are distributed in small range, usually several minutes
61
     * and overflow every 49 days if nxt_msec_t is stored in 32 bits.
62
     * This signed comparison takes into account that overflow.
63
     */
64
                      /* timer1->time < timer2->time */
65
0
    return nxt_msec_diff(timer1->time , timer2->time);
66
0
}
67
68
69
void
70
nxt_timer_add(nxt_event_engine_t *engine, nxt_timer_t *timer,
71
    nxt_msec_t timeout)
72
0
{
73
0
    int32_t   diff;
74
0
    uint32_t  time;
75
76
0
    time = engine->timers.now + timeout;
77
78
0
    nxt_debug(timer->task, "timer add: %M±%d %M:%M",
79
0
              timer->time, timer->bias, timeout, time);
80
81
0
    timer->enabled = 1;
82
83
0
    if (nxt_timer_is_in_tree(timer)) {
84
85
0
        diff = nxt_msec_diff(time, timer->time);
86
        /*
87
         * Use the previous timer if difference between it and the
88
         * new timer is within bias: this decreases number of rbtree
89
         * operations for fast connections.
90
         */
91
0
        if (nxt_abs(diff) <= timer->bias) {
92
0
            nxt_debug(timer->task, "timer previous: %M±%d",
93
0
                      time, timer->bias);
94
95
0
            nxt_timer_change(engine, timer, NXT_TIMER_NOPE, 0);
96
0
            return;
97
0
        }
98
0
    }
99
100
0
    nxt_timer_change(engine, timer, NXT_TIMER_ADD, time);
101
0
}
102
103
104
nxt_bool_t
105
nxt_timer_delete(nxt_event_engine_t *engine, nxt_timer_t *timer)
106
0
{
107
0
    nxt_debug(timer->task, "timer delete: %M±%d",
108
0
              timer->time, timer->bias);
109
110
0
    timer->enabled = 0;
111
112
0
    if (nxt_timer_is_in_tree(timer)) {
113
114
0
        nxt_timer_change(engine, timer, NXT_TIMER_DELETE, 0);
115
116
0
        return 1;
117
0
    }
118
119
0
    nxt_timer_change(engine, timer, NXT_TIMER_NOPE, 0);
120
121
0
    return (timer->queued || timer->change != NXT_TIMER_NO_CHANGE);
122
0
}
123
124
125
static void
126
nxt_timer_change(nxt_event_engine_t *engine, nxt_timer_t *timer,
127
    nxt_timer_operation_t change, nxt_msec_t time)
128
0
{
129
0
    nxt_timers_t        *timers;
130
0
    nxt_timer_change_t  *ch;
131
132
0
    timers = &engine->timers;
133
134
0
    if (timer->change == NXT_TIMER_NO_CHANGE) {
135
136
0
        if (change == NXT_TIMER_NOPE) {
137
0
            return;
138
0
        }
139
140
0
        if (timers->nchanges >= timers->mchanges) {
141
0
            nxt_timer_changes_commit(engine);
142
0
        }
143
144
0
        timers->nchanges++;
145
0
        timer->change = timers->nchanges;
146
0
    }
147
148
0
    nxt_debug(timer->task, "timer change: %M±%d:%d",
149
0
              time, timer->bias, change);
150
151
0
    ch = &timers->changes[timer->change - 1];
152
153
0
    ch->change = change;
154
0
    ch->time = time;
155
0
    ch->timer = timer;
156
0
}
157
158
159
static void
160
nxt_timer_changes_commit(nxt_event_engine_t *engine)
161
0
{
162
0
    nxt_timer_t         *timer;
163
0
    nxt_timers_t        *timers;
164
0
    nxt_timer_change_t  *ch, *end, *add, *add_end;
165
166
0
    timers = &engine->timers;
167
168
0
    nxt_debug(&engine->task, "timers changes: %ui", timers->nchanges);
169
170
0
    ch = timers->changes;
171
0
    end = ch + timers->nchanges;
172
173
0
    add = ch;
174
0
    add_end = add;
175
176
0
    while (ch < end) {
177
0
        timer = ch->timer;
178
179
0
        switch (ch->change) {
180
181
0
        case NXT_TIMER_NOPE:
182
0
            break;
183
184
0
        case NXT_TIMER_ADD:
185
186
0
            timer->time = ch->time;
187
188
0
            add_end->timer = timer;
189
0
            add_end++;
190
191
0
            if (!nxt_timer_is_in_tree(timer)) {
192
0
                break;
193
0
            }
194
195
            /* Fall through. */
196
197
0
        case NXT_TIMER_DELETE:
198
0
            nxt_debug(timer->task, "timer rbtree delete: %M±%d",
199
0
                      timer->time, timer->bias);
200
201
0
            nxt_rbtree_delete(&timers->tree, &timer->node);
202
0
            nxt_timer_in_tree_clear(timer);
203
204
0
            break;
205
0
        }
206
207
0
        timer->change = NXT_TIMER_NO_CHANGE;
208
209
0
        ch++;
210
0
    }
211
212
0
    while (add < add_end) {
213
0
        timer = add->timer;
214
215
0
        nxt_debug(timer->task, "timer rbtree insert: %M±%d",
216
0
                  timer->time, timer->bias);
217
218
0
        nxt_rbtree_insert(&timers->tree, &timer->node);
219
0
        nxt_timer_in_tree_set(timer);
220
221
0
        add++;
222
0
    }
223
224
0
    timers->nchanges = 0;
225
0
}
226
227
228
nxt_msec_t
229
nxt_timer_find(nxt_event_engine_t *engine)
230
0
{
231
0
    int32_t            delta;
232
0
    nxt_msec_t         time;
233
0
    nxt_timer_t        *timer;
234
0
    nxt_timers_t       *timers;
235
0
    nxt_rbtree_t       *tree;
236
0
    nxt_rbtree_node_t  *node, *next;
237
238
0
    timers = &engine->timers;
239
240
0
    if (timers->nchanges != 0) {
241
0
        nxt_timer_changes_commit(engine);
242
0
    }
243
244
0
    tree = &timers->tree;
245
246
0
    for (node = nxt_rbtree_min(tree);
247
0
         nxt_rbtree_is_there_successor(tree, node);
248
0
         node = next)
249
0
    {
250
0
        next = nxt_rbtree_node_successor(tree, node);
251
252
0
        timer = (nxt_timer_t *) node;
253
254
        /*
255
         * Disabled timers are not deleted here since the minimum active
256
         * timer may be larger than a disabled timer, but event poll may
257
         * return much earlier and the disabled timer can be reactivated.
258
         */
259
260
0
        if (timer->enabled) {
261
0
            time = timer->time;
262
0
            timers->minimum = time - timer->bias;
263
264
0
            nxt_debug(timer->task, "timer found minimum: %M±%d:%M",
265
0
                      time, timer->bias, timers->now);
266
267
0
            delta = nxt_msec_diff(time, timers->now);
268
269
0
            return (nxt_msec_t) nxt_max(delta, 0);
270
0
        }
271
0
    }
272
273
    /* Set minimum time one day ahead. */
274
0
    timers->minimum = timers->now + 24 * 60 * 60 * 1000;
275
276
0
    return NXT_INFINITE_MSEC;
277
0
}
278
279
280
void
281
nxt_timer_expire(nxt_event_engine_t *engine, nxt_msec_t now)
282
0
{
283
0
    nxt_timer_t        *timer;
284
0
    nxt_timers_t       *timers;
285
0
    nxt_rbtree_t       *tree;
286
0
    nxt_rbtree_node_t  *node, *next;
287
288
0
    timers = &engine->timers;
289
0
    timers->now = now;
290
291
0
    nxt_debug(&engine->task, "timer expire minimum: %M:%M",
292
0
              timers->minimum, now);
293
294
                   /* timers->minimum > now */
295
0
    if (nxt_msec_diff(timers->minimum , now) > 0) {
296
0
        return;
297
0
    }
298
299
0
    tree = &timers->tree;
300
301
0
    for (node = nxt_rbtree_min(tree);
302
0
         nxt_rbtree_is_there_successor(tree, node);
303
0
         node = next)
304
0
    {
305
0
        timer = (nxt_timer_t *) node;
306
307
                       /* timer->time > now + timer->bias */
308
0
        if (nxt_msec_diff(timer->time , now) > (int32_t) timer->bias) {
309
0
            return;
310
0
        }
311
312
0
        next = nxt_rbtree_node_successor(tree, node);
313
314
0
        nxt_debug(timer->task, "timer expire delete: %M±%d",
315
0
                  timer->time, timer->bias);
316
317
0
        nxt_rbtree_delete(tree, &timer->node);
318
0
        nxt_timer_in_tree_clear(timer);
319
320
0
        if (timer->enabled) {
321
0
            timer->queued = 1;
322
323
0
            nxt_work_queue_add(timer->work_queue, nxt_timer_handler,
324
0
                               timer->task, timer, NULL);
325
0
        }
326
0
    }
327
0
}
328
329
330
static void
331
nxt_timer_handler(nxt_task_t *task, void *obj, void *data)
332
0
{
333
0
    nxt_timer_t  *timer;
334
335
0
    timer = obj;
336
337
0
    timer->queued = 0;
338
339
0
    if (timer->enabled && timer->change == NXT_TIMER_NO_CHANGE) {
340
0
        timer->enabled = 0;
341
342
        timer->handler(task, timer, NULL);
343
0
    }
344
0
}