Coverage Report

Created: 2025-08-26 07:04

/src/unit/src/nxt_thread_time.c
Line
Count
Source (jump to first uncovered line)
1
2
/*
3
 * Copyright (C) Igor Sysoev
4
 * Copyright (C) NGINX, Inc.
5
 */
6
7
#include <nxt_main.h>
8
9
10
/*
11
 * Each thread keeps several time representations in its thread local
12
 * storage:
13
 *   the monotonic time in nanoseconds since unspecified point in the past,
14
 *   the real time in seconds and nanoseconds since the Epoch,
15
 *   the local time and GMT time structs,
16
 *   and various user-defined text representations of local and GMT times.
17
 *
18
 * The monotonic time is used mainly by engine timers and is updated after
19
 * a kernel operation which can block for unpredictable duration like event
20
 * polling.  Besides getting the monotonic time is generally faster than
21
 * getting the real time, so the monotonic time is also used for milestones
22
 * to update cached real time seconds and, if debug log enabled, milliseconds.
23
 * As a result, the cached real time is updated at most one time per second
24
 * or millisecond respectively.  If there is a signal event support or in
25
 * multi-threaded mode, then the cached real time and local time structs
26
 * are updated only on demand.  In single-threaded mode without the signal
27
 * event support the cached real and local time are updated synchronously
28
 * with the monotonic time update.  GMT time structs and text representations
29
 * are always updated only on demand.
30
 */
31
32
33
static void nxt_time_thread(void *data);
34
static void nxt_thread_time_shared(nxt_monotonic_time_t *now);
35
static void nxt_thread_realtime_update(nxt_thread_t *thr,
36
    nxt_monotonic_time_t *now);
37
static u_char *nxt_thread_time_string_no_cache(nxt_thread_t *thr,
38
    nxt_time_string_t *ts, u_char *buf);
39
static nxt_atomic_uint_t nxt_thread_time_string_slot(nxt_time_string_t *ts);
40
static nxt_time_string_cache_t *nxt_thread_time_string_cache(nxt_thread_t *thr,
41
    nxt_atomic_uint_t slot);
42
43
44
static nxt_atomic_int_t               nxt_gmtoff;
45
static nxt_bool_t                     nxt_use_shared_time = 0;
46
static volatile nxt_monotonic_time_t  nxt_shared_time;
47
48
49
void
50
nxt_thread_time_update(nxt_thread_t *thr)
51
2
{
52
2
    if (nxt_use_shared_time) {
53
0
        nxt_thread_time_shared(&thr->time.now);
54
55
2
    } else {
56
2
        nxt_monotonic_time(&thr->time.now);
57
2
    }
58
2
}
59
60
61
void
62
nxt_thread_time_free(nxt_thread_t *thr)
63
0
{
64
0
    nxt_uint_t               i;
65
0
    nxt_time_string_cache_t  *tsc;
66
67
0
    tsc = thr->time.strings;
68
69
0
    if (tsc) {
70
0
        thr->time.no_cache = 1;
71
72
0
        for (i = 0; i < thr->time.nstrings; i++) {
73
0
            nxt_free(tsc[i].string.start);
74
0
        }
75
76
0
        nxt_free(tsc);
77
0
        thr->time.strings = NULL;
78
0
    }
79
0
}
80
81
82
void
83
nxt_time_thread_start(nxt_msec_t interval)
84
0
{
85
0
    nxt_thread_link_t    *link;
86
0
    nxt_thread_handle_t  handle;
87
88
0
    link = nxt_zalloc(sizeof(nxt_thread_link_t));
89
90
0
    if (nxt_fast_path(link != NULL)) {
91
0
        link->start = nxt_time_thread;
92
0
        link->work.data = (void *) (uintptr_t) interval;
93
94
0
        (void) nxt_thread_create(&handle, link);
95
0
    }
96
0
}
97
98
99
static void
100
nxt_time_thread(void *data)
101
0
{
102
0
    nxt_nsec_t            interval, rest;
103
0
    nxt_thread_t          *thr;
104
0
    nxt_monotonic_time_t  now;
105
106
0
    interval = (uintptr_t) data;
107
0
    interval *= 1000000;
108
109
0
    thr = nxt_thread();
110
    /*
111
     * The time thread is never preempted by asynchronous signals, since
112
     * the signals are processed synchronously by dedicated thread.
113
     */
114
0
    thr->time.signal = -1;
115
116
0
    nxt_log_debug(thr->log, "time thread");
117
118
0
    nxt_memzero(&now, sizeof(nxt_monotonic_time_t));
119
120
0
    nxt_monotonic_time(&now);
121
0
    nxt_thread_realtime_update(thr, &now);
122
123
0
    nxt_shared_time = now;
124
0
    nxt_use_shared_time = 1;
125
126
0
    for ( ;; ) {
127
0
        rest = 1000000000 - now.realtime.nsec;
128
129
0
        nxt_nanosleep(nxt_min(interval, rest));
130
131
0
        nxt_monotonic_time(&now);
132
0
        nxt_thread_realtime_update(thr, &now);
133
134
0
        nxt_shared_time = now;
135
136
#if 0
137
        thr->time.now = now;
138
        nxt_log_debug(thr->log, "time thread");
139
#endif
140
141
#if 0
142
        if (nxt_exiting) {
143
            nxt_use_shared_time = 0;
144
            return;
145
        }
146
#endif
147
0
    }
148
0
}
149
150
151
static void
152
nxt_thread_time_shared(nxt_monotonic_time_t *now)
153
0
{
154
0
    nxt_uint_t  n;
155
0
    nxt_time_t  t;
156
0
    nxt_nsec_t  m, u;
157
158
    /* Lock-free thread time update. */
159
160
0
    for ( ;; ) {
161
0
        *now = nxt_shared_time;
162
163
0
        t = nxt_shared_time.realtime.sec;
164
0
        n = nxt_shared_time.realtime.nsec;
165
0
        m = nxt_shared_time.monotonic;
166
0
        u = nxt_shared_time.update;
167
168
0
        if (now->realtime.sec == t && now->realtime.nsec == n
169
0
            && now->monotonic == m && now->update == u)
170
0
        {
171
0
            return;
172
0
        }
173
0
    }
174
0
}
175
176
177
nxt_time_t
178
nxt_thread_time(nxt_thread_t *thr)
179
0
{
180
0
    nxt_thread_realtime_update(thr, &thr->time.now);
181
182
0
    return thr->time.now.realtime.sec;
183
0
}
184
185
186
nxt_realtime_t *
187
nxt_thread_realtime(nxt_thread_t *thr)
188
0
{
189
0
    nxt_thread_realtime_update(thr, &thr->time.now);
190
191
0
    return &thr->time.now.realtime;
192
0
}
193
194
195
static void
196
nxt_thread_realtime_update(nxt_thread_t *thr, nxt_monotonic_time_t *now)
197
0
{
198
0
    nxt_nsec_t  delta;
199
200
#if (NXT_DEBUG)
201
202
    if (nxt_slow_path(thr->log->level == NXT_LOG_DEBUG || nxt_debug)) {
203
204
        if (now->monotonic >= now->update) {
205
            nxt_realtime(&now->realtime);
206
207
            delta = 1000000 - now->realtime.nsec % 1000000;
208
            now->update = now->monotonic + delta;
209
        }
210
211
        return;
212
    }
213
214
#endif
215
216
0
    if (now->monotonic >= now->update) {
217
0
        nxt_realtime(&now->realtime);
218
219
0
        delta = 1000000000 - now->realtime.nsec;
220
0
        now->update = now->monotonic + delta;
221
0
    }
222
0
}
223
224
225
u_char *
226
nxt_thread_time_string(nxt_thread_t *thr, nxt_time_string_t *ts, u_char *buf)
227
0
{
228
0
    u_char                   *p;
229
0
    struct tm                *tm;
230
0
    nxt_time_t               s;
231
0
    nxt_bool_t               update;
232
0
    nxt_atomic_uint_t        slot;
233
0
    nxt_time_string_cache_t  *tsc;
234
235
0
    if (nxt_slow_path(thr == NULL || thr->time.no_cache)) {
236
0
        return nxt_thread_time_string_no_cache(thr, ts, buf);
237
0
    }
238
239
0
    slot = nxt_thread_time_string_slot(ts);
240
241
0
    tsc = nxt_thread_time_string_cache(thr, slot);
242
0
    if (tsc == NULL) {
243
0
        return buf;
244
0
    }
245
246
0
    if (thr->time.signal < 0) {
247
        /*
248
         * Lazy real time update:
249
         * signal event support or multi-threaded mode.
250
         */
251
0
        nxt_thread_realtime_update(thr, &thr->time.now);
252
0
    }
253
254
0
    s = thr->time.now.realtime.sec;
255
256
0
    update = (s != tsc->last);
257
258
#if (NXT_DEBUG)
259
260
    if (ts->msec == NXT_THREAD_TIME_MSEC
261
        && (nxt_slow_path(thr->log->level == NXT_LOG_DEBUG || nxt_debug)))
262
    {
263
        nxt_msec_t  ms;
264
265
        ms = thr->time.now.realtime.nsec / 1000000;
266
        update |= (ms != tsc->last_msec);
267
        tsc->last_msec = ms;
268
    }
269
270
#endif
271
272
0
    if (nxt_slow_path(update)) {
273
274
0
        if (ts->timezone == NXT_THREAD_TIME_LOCAL) {
275
276
0
            tm = &thr->time.localtime;
277
278
0
            if (nxt_slow_path(s != thr->time.last_localtime)) {
279
280
0
                if (thr->time.signal < 0) {
281
                    /*
282
                     * Lazy local time update:
283
                     * signal event support or multi-threaded mode.
284
                     */
285
0
                    nxt_localtime(s, &thr->time.localtime);
286
0
                    thr->time.last_localtime = s;
287
288
0
                } else {
289
                    /*
290
                     * "thr->time.signal >= 0" means that a thread may be
291
                     * interrupted by a signal handler.  Since localtime()
292
                     * cannot be safely called in a signal context, the
293
                     * thread's thr->time.localtime must be updated regularly
294
                     * by nxt_thread_time_update() in non-signal context.
295
                     * Stale timestamp means that nxt_thread_time_string()
296
                     * is being called in a signal context, so here is
297
                     * Async-Signal-Safe localtime() emulation using the
298
                     * latest cached GMT offset.
299
                     *
300
                     * The timestamp is not set here intentionally to update
301
                     * thr->time.localtime later in non-signal context.  The
302
                     * real previously cached thr->localtime is used because
303
                     * Linux and Solaris strftime() depend on tm.tm_isdst
304
                     * and tm.tm_gmtoff fields.
305
                     */
306
0
                    nxt_gmtime(s + nxt_timezone(tm), tm);
307
0
                }
308
0
            }
309
310
0
        } else {
311
0
            tm = &thr->time.gmtime;
312
313
0
            if (nxt_slow_path(s != thr->time.last_gmtime)) {
314
0
                nxt_gmtime(s, tm);
315
0
                thr->time.last_gmtime = s;
316
0
            }
317
318
0
        }
319
320
0
        p = tsc->string.start;
321
322
0
        if (nxt_slow_path(p == NULL)) {
323
324
0
            thr->time.no_cache = 1;
325
0
            p = nxt_zalloc(ts->size);
326
0
            thr->time.no_cache = 0;
327
328
0
            if (p == NULL) {
329
0
                return buf;
330
0
            }
331
332
0
            tsc->string.start = p;
333
0
        }
334
335
0
        p = ts->handler(p, &thr->time.now.realtime, tm, ts->size, ts->format);
336
337
0
        tsc->string.length = p - tsc->string.start;
338
339
0
        if (nxt_slow_path(tsc->string.length == 0)) {
340
0
            return buf;
341
0
        }
342
343
0
        tsc->last = s;
344
0
    }
345
346
0
    return nxt_cpymem(buf, tsc->string.start, tsc->string.length);
347
0
}
348
349
350
static u_char *
351
nxt_thread_time_string_no_cache(nxt_thread_t *thr, nxt_time_string_t *ts,
352
    u_char *buf)
353
0
{
354
0
    struct tm       tm;
355
0
    nxt_realtime_t  now;
356
357
0
    nxt_realtime(&now);
358
359
0
    if (ts->timezone == NXT_THREAD_TIME_LOCAL) {
360
361
0
        if (thr == NULL || thr->time.signal <= 0) {
362
            /* Non-signal context */
363
0
            nxt_localtime(now.sec, &tm);
364
365
0
        } else {
366
0
            nxt_gmtime(now.sec + nxt_gmtoff, &tm);
367
0
        }
368
369
0
    } else {
370
0
        nxt_gmtime(now.sec, &tm);
371
0
    }
372
373
0
    return ts->handler(buf, &now, &tm, ts->size, ts->format);
374
0
}
375
376
377
static nxt_atomic_uint_t
378
nxt_thread_time_string_slot(nxt_time_string_t *ts)
379
0
{
380
0
    static nxt_atomic_t  slot;
381
382
0
    while (nxt_slow_path((nxt_atomic_int_t) ts->slot < 0)) {
383
        /*
384
         * Atomic allocation of a slot number.
385
         * -1 means an uninitialized slot,
386
         * -2 is the initializing lock to assure the single value for the slot.
387
         */
388
0
        if (nxt_atomic_cmp_set(&ts->slot, -1, -2)) {
389
0
            ts->slot = nxt_atomic_fetch_add(&slot, 1);
390
391
            /* No "break" here since it adds only dispensable "jmp". */
392
0
        }
393
0
    }
394
395
0
    return (nxt_atomic_uint_t) ts->slot;
396
0
}
397
398
399
static nxt_time_string_cache_t *
400
nxt_thread_time_string_cache(nxt_thread_t *thr, nxt_atomic_uint_t slot)
401
0
{
402
0
    size_t                   size;
403
0
    nxt_atomic_uint_t        i, nstrings;
404
0
    nxt_time_string_cache_t  *tsc;
405
406
0
    if (nxt_fast_path(slot < thr->time.nstrings)) {
407
0
        tsc = &thr->time.strings[slot];
408
0
        nxt_prefetch(tsc->string.start);
409
0
        return tsc;
410
0
    }
411
412
0
    nstrings = slot + 1;
413
0
    size = nstrings * sizeof(nxt_time_string_cache_t);
414
415
0
    thr->time.no_cache = 1;
416
0
    tsc = nxt_realloc(thr->time.strings, size);
417
0
    thr->time.no_cache = 0;
418
419
0
    if (tsc == NULL) {
420
0
        return NULL;
421
0
    }
422
423
0
    for (i = thr->time.nstrings; i < nstrings; i++) {
424
0
        tsc[i].last = -1;
425
0
        tsc[i].string.start = NULL;
426
0
    }
427
428
0
    thr->time.strings = tsc;
429
0
    thr->time.nstrings = nstrings;
430
431
0
    return &tsc[slot];
432
0
}