From acdf591833a7e563677a3e2450b566ebf99d018e Mon Sep 17 00:00:00 2001 From: Justine Tunney Date: Fri, 5 Aug 2022 19:24:05 -0700 Subject: [PATCH] Avoid long double timestamps in redbean --- libc/assert.h | 3 +- libc/calls/_timespec_eq.c | 26 ++++++ libc/calls/_timespec_frommicros.c | 29 +++++++ libc/calls/_timespec_frommillis.c | 29 +++++++ libc/calls/_timespec_gte.c | 28 +++++++ libc/calls/_timespec_mono.c | 31 +++++++ libc/calls/_timespec_real.c | 31 +++++++ libc/calls/_timespec_tomicros.c | 32 +++++++ libc/calls/_timespec_tomillis.c | 32 +++++++ libc/calls/calls.mk | 9 ++ libc/calls/struct/timespec.h | 13 ++- libc/calls/timespec_add.c | 4 +- libc/log/vflogf.c | 36 ++++---- test/libc/calls/_timespec_test.c | 76 +++++++++++++++++ test/libc/calls/clock_gettime_test.c | 1 + tool/net/help.txt | 7 +- tool/net/redbean.c | 121 +++++++++++++++------------ 17 files changed, 431 insertions(+), 77 deletions(-) create mode 100644 libc/calls/_timespec_eq.c create mode 100644 libc/calls/_timespec_frommicros.c create mode 100644 libc/calls/_timespec_frommillis.c create mode 100644 libc/calls/_timespec_gte.c create mode 100644 libc/calls/_timespec_mono.c create mode 100644 libc/calls/_timespec_real.c create mode 100644 libc/calls/_timespec_tomicros.c create mode 100644 libc/calls/_timespec_tomillis.c create mode 100644 test/libc/calls/_timespec_test.c diff --git a/libc/assert.h b/libc/assert.h index ce6933b2a..e7c8d22e4 100644 --- a/libc/assert.h +++ b/libc/assert.h @@ -1,5 +1,6 @@ #ifndef COSMOPOLITAN_LIBC_ASSERT_H_ #define COSMOPOLITAN_LIBC_ASSERT_H_ +#include "libc/bits/likely.h" #if !(__ASSEMBLER__ + __LINKER__ + 0) COSMOPOLITAN_C_START_ @@ -10,7 +11,7 @@ void __assert_fail(const char *, const char *, int) hidden relegated; #define assert(EXPR) ((void)0) #else #define assert(EXPR) \ - ((void)((EXPR) || (__assert_fail(#EXPR, __FILE__, __LINE__), 0))) + ((void)(LIKELY(EXPR) || (__assert_fail(#EXPR, __FILE__, __LINE__), 0))) #endif #ifndef __cplusplus diff --git a/libc/calls/_timespec_eq.c b/libc/calls/_timespec_eq.c new file mode 100644 index 000000000..e0a1b9ccb --- /dev/null +++ b/libc/calls/_timespec_eq.c @@ -0,0 +1,26 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2022 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/struct/timespec.h" + +/** + * Checks if 𝑥 = 𝑦. + */ +bool _timespec_eq(struct timespec x, struct timespec y) { + return x.tv_sec == y.tv_sec && x.tv_nsec == y.tv_nsec; +} diff --git a/libc/calls/_timespec_frommicros.c b/libc/calls/_timespec_frommicros.c new file mode 100644 index 000000000..261faba9a --- /dev/null +++ b/libc/calls/_timespec_frommicros.c @@ -0,0 +1,29 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2022 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/struct/timespec.h" + +/** + * Converts timespec interval from microseconds. + */ +struct timespec _timespec_frommicros(int64_t x) { + struct timespec ts; + ts.tv_sec = x / 1000000; + ts.tv_nsec = x % 1000000 * 1000; + return ts; +} diff --git a/libc/calls/_timespec_frommillis.c b/libc/calls/_timespec_frommillis.c new file mode 100644 index 000000000..e52cb6e5e --- /dev/null +++ b/libc/calls/_timespec_frommillis.c @@ -0,0 +1,29 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2022 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/struct/timespec.h" + +/** + * Converts timespec interval from milliseconds. + */ +struct timespec _timespec_frommillis(int64_t x) { + struct timespec ts; + ts.tv_sec = x / 1000; + ts.tv_nsec = x % 1000 * 1000000; + return ts; +} diff --git a/libc/calls/_timespec_gte.c b/libc/calls/_timespec_gte.c new file mode 100644 index 000000000..3b197a6fa --- /dev/null +++ b/libc/calls/_timespec_gte.c @@ -0,0 +1,28 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2022 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/struct/timespec.h" + +/** + * Checks if 𝑥 ≥ 𝑦. + */ +bool _timespec_gte(struct timespec x, struct timespec y) { + if (x.tv_sec > y.tv_sec) return true; + if (x.tv_sec < y.tv_sec) return false; + return x.tv_nsec >= y.tv_nsec; +} diff --git a/libc/calls/_timespec_mono.c b/libc/calls/_timespec_mono.c new file mode 100644 index 000000000..191784f4f --- /dev/null +++ b/libc/calls/_timespec_mono.c @@ -0,0 +1,31 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2022 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/assert.h" +#include "libc/calls/struct/timespec.h" +#include "libc/sysv/consts/clock.h" +#include "libc/sysv/consts/nr.h" +#include "libc/time/time.h" + +struct timespec _timespec_mono(void) { + int ax, dx; + struct timespec ts; + ax = clock_gettime(CLOCK_MONOTONIC_FAST, &ts); + assert(!ax); + return ts; +} diff --git a/libc/calls/_timespec_real.c b/libc/calls/_timespec_real.c new file mode 100644 index 000000000..805c4b9d6 --- /dev/null +++ b/libc/calls/_timespec_real.c @@ -0,0 +1,31 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2022 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/assert.h" +#include "libc/calls/struct/timespec.h" +#include "libc/sysv/consts/clock.h" +#include "libc/sysv/consts/nr.h" +#include "libc/time/time.h" + +struct timespec _timespec_real(void) { + int ax, dx; + struct timespec ts; + ax = clock_gettime(CLOCK_REALTIME_FAST, &ts); + assert(!ax); + return ts; +} diff --git a/libc/calls/_timespec_tomicros.c b/libc/calls/_timespec_tomicros.c new file mode 100644 index 000000000..88757de56 --- /dev/null +++ b/libc/calls/_timespec_tomicros.c @@ -0,0 +1,32 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2022 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/struct/timespec.h" +#include "libc/limits.h" + +/** + * Converts timespec interval to microseconds. + */ +int64_t _timespec_tomicros(struct timespec x) { + int64_t us; + if (!__builtin_add_overflow(x.tv_sec, x.tv_nsec / 1000, &us)) { + return us; + } else { + return INT64_MAX; + } +} diff --git a/libc/calls/_timespec_tomillis.c b/libc/calls/_timespec_tomillis.c new file mode 100644 index 000000000..7d98127bf --- /dev/null +++ b/libc/calls/_timespec_tomillis.c @@ -0,0 +1,32 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2022 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/struct/timespec.h" +#include "libc/limits.h" + +/** + * Converts timespec interval to milliseconds. + */ +int64_t _timespec_tomillis(struct timespec x) { + int64_t us; + if (!__builtin_add_overflow(x.tv_sec, x.tv_nsec / 1000000, &us)) { + return us; + } else { + return INT64_MAX; + } +} diff --git a/libc/calls/calls.mk b/libc/calls/calls.mk index 4de9fca70..1dca6c8a0 100644 --- a/libc/calls/calls.mk +++ b/libc/calls/calls.mk @@ -179,6 +179,15 @@ o//libc/calls/getcwd-xnu.greg.o: \ OVERRIDE_CFLAGS += \ -Os +# we always want -O2 because: +# division is expensive if not optimized +o/$(MODE)/libc/calls/_timespec_tomillis.o \ +o/$(MODE)/libc/calls/_timespec_tomicros.o \ +o/$(MODE)/libc/calls/_timespec_frommillis.o \ +o/$(MODE)/libc/calls/_timespec_frommicros.o: \ + OVERRIDE_CFLAGS += \ + -O2 + o/$(MODE)/libc/calls/pledge.o \ o/$(MODE)/libc/calls/unveil.o: \ OVERRIDE_CFLAGS += \ diff --git a/libc/calls/struct/timespec.h b/libc/calls/struct/timespec.h index 720d9c480..6e9b454e2 100644 --- a/libc/calls/struct/timespec.h +++ b/libc/calls/struct/timespec.h @@ -8,9 +8,16 @@ struct timespec { }; int sys_futex(int *, int, int, const struct timespec *, int *); -bool _timespec_gt(struct timespec, struct timespec); -struct timespec _timespec_add(struct timespec, struct timespec); -struct timespec _timespec_sub(struct timespec, struct timespec); +int64_t _timespec_tomicros(struct timespec) pureconst; +int64_t _timespec_tomillis(struct timespec) pureconst; +struct timespec _timespec_frommicros(int64_t) pureconst; +struct timespec _timespec_frommillis(int64_t) pureconst; +bool _timespec_eq(struct timespec, struct timespec) pureconst; +bool _timespec_gte(struct timespec, struct timespec) pureconst; +struct timespec _timespec_add(struct timespec, struct timespec) pureconst; +struct timespec _timespec_sub(struct timespec, struct timespec) pureconst; +struct timespec _timespec_real(void); +struct timespec _timespec_mono(void); #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ #endif /* COSMOPOLITAN_LIBC_CALLS_STRUCT_TIMESPEC_H_ */ diff --git a/libc/calls/timespec_add.c b/libc/calls/timespec_add.c index 163cddf1e..ae9627da5 100644 --- a/libc/calls/timespec_add.c +++ b/libc/calls/timespec_add.c @@ -24,8 +24,8 @@ struct timespec _timespec_add(struct timespec x, struct timespec y) { x.tv_sec += y.tv_sec; x.tv_nsec += y.tv_nsec; - if (x.tv_nsec >= 10000000000) { - x.tv_nsec -= 10000000000; + if (x.tv_nsec >= 1000000000) { + x.tv_nsec -= 1000000000; x.tv_sec += 1; } return x; diff --git a/libc/log/vflogf.c b/libc/log/vflogf.c index 9d4b4e191..6307bcb81 100644 --- a/libc/log/vflogf.c +++ b/libc/log/vflogf.c @@ -85,35 +85,39 @@ static void vflogf_onfail(FILE *f) { void(vflogf)(unsigned level, const char *file, int line, FILE *f, const char *fmt, va_list va) { int bufmode; + int64_t dots; struct tm tm; - long double t2; - const char *prog; - bool issamesecond; char buf32[32]; - int64_t secs, nsec, dots; + const char *prog; + const char *sign; + bool issamesecond; + struct timespec t2; if (!f) f = __log_file; if (!f) return; flockfile(f); --__strace; - t2 = nowl(); - secs = t2; - nsec = (t2 - secs) * 1e9L; - issamesecond = secs == vflogf_ts.tv_sec; - dots = issamesecond ? nsec - vflogf_ts.tv_nsec : nsec; - vflogf_ts.tv_sec = secs; - vflogf_ts.tv_nsec = nsec; + // We display TIMESTAMP.MICROS normally. However, when we log multiple + // times in the same second, we display TIMESTAMP+DELTAMICROS instead. + t2 = _timespec_real(); + if (t2.tv_sec == vflogf_ts.tv_sec) { + sign = "+"; + dots = t2.tv_nsec - vflogf_ts.tv_nsec; + } else { + sign = "."; + dots = t2.tv_nsec; + } + vflogf_ts = t2; - localtime_r(&secs, &tm); - strcpy(iso8601(buf32, &tm), issamesecond ? "+" : "."); + localtime_r(&t2.tv_sec, &tm); + strcpy(iso8601(buf32, &tm), sign); prog = basename(firstnonnull(program_invocation_name, "unknown")); bufmode = f->bufmode; if (bufmode == _IOLBF) f->bufmode = _IOFBF; if ((fprintf_unlocked)(f, "%r%c%s%06ld:%s:%d:%.*s:%d] ", - "FEWIVDNT"[level & 7], buf32, dots / 1000 % 1000000, - file, line, strchrnul(prog, '.') - prog, prog, - getpid()) <= 0) { + "FEWIVDNT"[level & 7], buf32, dots / 1000, file, line, + strchrnul(prog, '.') - prog, prog, getpid()) <= 0) { vflogf_onfail(f); } (vfprintf_unlocked)(f, fmt, va); diff --git a/test/libc/calls/_timespec_test.c b/test/libc/calls/_timespec_test.c new file mode 100644 index 000000000..46e29eabd --- /dev/null +++ b/test/libc/calls/_timespec_test.c @@ -0,0 +1,76 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2022 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/struct/timespec.h" +#include "libc/intrin/kprintf.h" +#include "libc/rand/rand.h" +#include "libc/testlib/testlib.h" + +TEST(_timespec_gte, test) { + EXPECT_FALSE(_timespec_gte((struct timespec){1}, (struct timespec){2})); + EXPECT_TRUE(_timespec_gte((struct timespec){2}, (struct timespec){2})); + EXPECT_TRUE(_timespec_gte((struct timespec){2}, (struct timespec){1})); + EXPECT_FALSE(_timespec_gte((struct timespec){2}, (struct timespec){2, 1})); + EXPECT_TRUE(_timespec_gte((struct timespec){2, 1}, (struct timespec){2})); +} + +TEST(_timespec_sub, test) { + EXPECT_TRUE( + _timespec_eq((struct timespec){-1}, + _timespec_sub((struct timespec){1}, (struct timespec){2}))); + EXPECT_TRUE( + _timespec_eq((struct timespec){1}, + _timespec_sub((struct timespec){2}, (struct timespec){1}))); + EXPECT_TRUE(_timespec_eq( + (struct timespec){1, 1}, + _timespec_sub((struct timespec){2, 2}, (struct timespec){1, 1}))); + EXPECT_TRUE(_timespec_eq( + (struct timespec){0, 999999999}, + _timespec_sub((struct timespec){2, 1}, (struct timespec){1, 2}))); +} + +TEST(_timespec_frommillis, test) { + EXPECT_TRUE( + _timespec_eq((struct timespec){0, 1000000}, _timespec_frommillis(1))); + EXPECT_TRUE( + _timespec_eq((struct timespec){0, 2000000}, _timespec_frommillis(2))); + EXPECT_TRUE(_timespec_eq((struct timespec){1}, _timespec_frommillis(1000))); +} + +TEST(_timespec_frommicros, test) { + EXPECT_TRUE( + _timespec_eq((struct timespec){0, 1000}, _timespec_frommicros(1))); + EXPECT_TRUE( + _timespec_eq((struct timespec){0, 2000}, _timespec_frommicros(2))); + EXPECT_TRUE( + _timespec_eq((struct timespec){1}, _timespec_frommicros(1000000))); +} + +static long mod(long x, long y) { + if (y == -1) return 0; + return x - y * (x / y - (x % y && (x ^ y) < 0)); +} + +TEST(_timespec_sub, math) { + for (int i = 0; i < 1000; ++i) { + struct timespec x = {mod(lemur64(), 10), mod(lemur64(), 10)}; + struct timespec y = {mod(lemur64(), 10), mod(lemur64(), 10)}; + struct timespec z = _timespec_add(_timespec_sub(x, y), y); + EXPECT_TRUE(_timespec_eq(x, _timespec_add(_timespec_sub(x, y), y))); + } +} diff --git a/test/libc/calls/clock_gettime_test.c b/test/libc/calls/clock_gettime_test.c index 97264ff93..78de1fc62 100644 --- a/test/libc/calls/clock_gettime_test.c +++ b/test/libc/calls/clock_gettime_test.c @@ -55,6 +55,7 @@ BENCH(clock_gettime, bench) { EZBENCH2("nowl", donothing, nowl()); EZBENCH2("rdtsc", donothing, rdtsc()); EZBENCH2("gettimeofday", donothing, gettimeofday(&tv, 0)); + EZBENCH2("_timespec_real", donothing, _timespec_real()); EZBENCH2("clock_gettime 0", donothing, clock_gettime(CLOCK_REALTIME_FAST, &ts)); EZBENCH2("clock_gettime 1", donothing, diff --git a/tool/net/help.txt b/tool/net/help.txt index c3dd4c4b7..2c6abe660 100644 --- a/tool/net/help.txt +++ b/tool/net/help.txt @@ -1374,9 +1374,10 @@ FUNCTIONS as Content-Range and Date, which are abstracted by the transport layer. - ProgramHeartbeatInterval(milliseconds:int) - Sets the heartbeat interval (in milliseconds). 5000ms is the default - and 100ms is the minimum. + ProgramHeartbeatInterval([milliseconds:int]) + Sets the heartbeat interval (in milliseconds). 5000ms is the + default and 100ms is the minimum. If `milliseconds` is not + specified, then the current interval is returned. ProgramTimeout(milliseconds:int|seconds:int) Default timeout is 60000ms. Minimal value of timeout is 10(ms). diff --git a/tool/net/redbean.c b/tool/net/redbean.c index 67be08455..50698416d 100644 --- a/tool/net/redbean.c +++ b/tool/net/redbean.c @@ -328,9 +328,9 @@ static struct Assets { static struct Shared { int workers; - long double nowish; - long double lastreindex; - long double lastmeltdown; + struct timespec nowish; + struct timespec lastreindex; + struct timespec lastmeltdown; char currentdate[32]; struct rusage server; struct rusage children; @@ -430,7 +430,6 @@ static int sslpskindex; static int oldloglevel; static int messageshandled; static int sslticketlifetime; -static int heartbeatint = 5000; // ms static uint32_t clientaddrsize; static size_t zsize; @@ -470,16 +469,17 @@ static struct Buffer oldin; static struct Buffer hdrbuf; static struct timeval timeout; static struct Buffer effectivepath; +static struct timespec heartbeatinterval; static struct Url url; static struct stat zst; -static long double startread; -static long double lastrefresh; -static long double startserver; -static long double startrequest; -static long double lastheartbeat; -static long double startconnection; +static struct timespec startread; +static struct timespec lastrefresh; +static struct timespec startserver; +static struct timespec startrequest; +static struct timespec lastheartbeat; +static struct timespec startconnection; static struct sockaddr_in clientaddr; static struct sockaddr_in *serveraddr; @@ -1314,6 +1314,11 @@ static ssize_t ReadAll(int fd, char *p, size_t n) { return i; } +static bool IsTakingTooLong(void) { + return meltdown && _timespec_gte(_timespec_sub(_timespec_real(), startread), + (struct timespec){2}); +} + static ssize_t WritevAll(int fd, struct iovec *iov, int iovlen) { int i; ssize_t rc; @@ -1340,7 +1345,7 @@ static ssize_t WritevAll(int fd, struct iovec *iov, int iovlen) { } else if (errno == EINTR) { errno = 0; LockInc(&shared->c.writeinterruputs); - if (killed || (meltdown && nowl() - startread > 2)) { + if (killed || IsTakingTooLong()) { return total ? total : -1; } } else { @@ -1556,7 +1561,6 @@ static void CertsDestroy(void) { static void WipeServingKeys(void) { size_t i; - long double t = nowl(); if (uniprocess) return; mbedtls_ssl_ticket_free(&ssltick); mbedtls_ssl_key_cert_free(conf.key_cert), conf.key_cert = 0; @@ -1675,7 +1679,7 @@ static bool TlsSetup(void) { return true; } else if (r == MBEDTLS_ERR_SSL_WANT_READ) { LockInc(&shared->c.handshakeinterrupts); - if (terminated || killed || (meltdown && nowl() - startread > 2)) { + if (terminated || killed || IsTakingTooLong()) { return false; } } else { @@ -1935,10 +1939,10 @@ char *FormatUnixHttpDateTime(char *s, int64_t t) { return s; } -static void UpdateCurrentDate(long double now) { +static void UpdateCurrentDate(struct timespec now) { int64_t t; struct tm tm; - t = now; + t = now.tv_sec; shared->nowish = now; gmtime_r(&t, &tm); FormatHttpDateTime(shared->currentdate, &tm); @@ -2169,7 +2173,7 @@ static char *AppendCache(char *p, int64_t seconds) { p = stpcpy(p, ", must-revalidate"); } p = AppendCrlf(p); - return AppendExpires(p, (int64_t)shared->nowish + seconds); + return AppendExpires(p, shared->nowish.tv_sec + seconds); } static inline char *AppendContentLength(char *p, size_t n) { @@ -2923,7 +2927,7 @@ td { padding-right: 3em; }\r\n\ } appends(&cpm.outbuf, "\r\n"); and = ""; - x = nowl() - startserver; + x = _timespec_sub(_timespec_real(), startserver).tv_sec; y = ldiv(x, 24L * 60 * 60); if (y.quot) { appendf(&cpm.outbuf, "%,ld day%s ", y.quot, y.quot == 1 ? "" : "s"); @@ -3012,12 +3016,12 @@ static char *ServeStatusz(void) { } AppendLong1("pid", getpid()); AppendLong1("ppid", getppid()); - AppendLong1("now", nowl()); - AppendLong1("nowish", shared->nowish); + AppendLong1("now", _timespec_real().tv_sec); + AppendLong1("nowish", shared->nowish.tv_sec); AppendLong1("gmtoff", gmtoff); AppendLong1("CLK_TCK", CLK_TCK); - AppendLong1("startserver", startserver); - AppendLong1("lastmeltdown", shared->lastmeltdown); + AppendLong1("startserver", startserver.tv_sec); + AppendLong1("lastmeltdown", shared->lastmeltdown.tv_sec); AppendLong1("workers", shared->workers); AppendLong1("assets.n", assets.n); #ifndef STATIC @@ -3436,7 +3440,7 @@ static void StoreAsset(char *path, size_t pathlen, char *data, size_t datalen, int i; uint32_t crc; char *comp, *p; - long double now; + struct timespec now; struct Asset *a; struct iovec v[13]; uint8_t *cdir, era; @@ -3479,14 +3483,14 @@ static void StoreAsset(char *path, size_t pathlen, char *data, size_t datalen, return; } OpenZip(false); - now = nowl(); + now = _timespec_real(); a = GetAssetZip(path, pathlen); if (!mode) mode = a ? GetMode(a) : 0644; if (!(mode & S_IFMT)) mode |= S_IFREG; if (pathlen > 1 && path[0] == '/') ++path, --pathlen; dosmode = !(mode & 0200) ? kNtFileAttributeReadonly : 0; - ft = (now + MODERNITYSECONDS) * HECTONANOSECONDS; - GetDosLocalTime(now, &mtime, &mdate); + ft = (now.tv_sec + MODERNITYSECONDS) * HECTONANOSECONDS; + GetDosLocalTime(now.tv_sec, &mtime, &mdate); // local file header if (uselen >= 0xffffffff || datalen >= 0xffffffff) { era = kZipEra2001; @@ -4169,7 +4173,7 @@ VerifyFailed: } static int LuaGetDate(lua_State *L) { - lua_pushinteger(L, shared->nowish); + lua_pushinteger(L, shared->nowish.tv_sec); return 1; } @@ -4753,12 +4757,14 @@ static int LuaProgramUniprocess(lua_State *L) { } static int LuaProgramHeartbeatInterval(lua_State *L) { + int64_t millis; OnlyCallFromMainProcess(L, "ProgramHeartbeatInterval"); - if (!lua_isinteger(L, 1) && !lua_isnoneornil(L, 1)) { - return luaL_argerror(L, 1, "invalid heartbeat interval; integer expected"); + if (!lua_isnoneornil(L, 1)) { + millis = luaL_checkinteger(L, 1); + millis = MAX(100, millis); + heartbeatinterval = _timespec_frommillis(millis); } - lua_pushinteger(L, heartbeatint); - if (lua_isinteger(L, 1)) heartbeatint = MAX(100, lua_tointeger(L, 1)); + lua_pushinteger(L, _timespec_tomillis(heartbeatinterval)); return 1; } @@ -5634,10 +5640,13 @@ Content-Length: 0\r\n\ } static void EnterMeltdownMode(void) { - if (shared->lastmeltdown && nowl() - shared->lastmeltdown < 1) return; + if (shared->lastmeltdown.tv_sec && + !_timespec_gte(_timespec_sub(_timespec_real(), shared->lastmeltdown), + (struct timespec){1})) + return; WARNF("(srvr) server is melting down (%,d workers)", shared->workers); LOGIFNEG1(kill(0, SIGUSR2)); - shared->lastmeltdown = nowl(); + shared->lastmeltdown = _timespec_real(); ++shared->c.meltdowns; } @@ -5767,8 +5776,7 @@ static void HandleReload(void) { static void HandleHeartbeat(void) { size_t i; sigset_t mask; - if (nowl() - lastrefresh > 60 * 60) RefreshTime(); - UpdateCurrentDate(nowl()); + UpdateCurrentDate(_timespec_real()); Reindex(); getrusage(RUSAGE_SELF, &shared->server); #ifndef STATIC @@ -5866,7 +5874,9 @@ static char *ReadMore(void) { amtread += got; } else if (errno == EINTR) { LockInc(&shared->c.readinterrupts); - if (killed || ((meltdown || terminated) && nowl() - startread > 1)) { + if (killed || ((meltdown || terminated) && + _timespec_gte(_timespec_sub(_timespec_real(), startread), + (struct timespec){1}))) { return HandlePayloadDrop(); } } else { @@ -6347,7 +6357,7 @@ static bool HandleMessageActual(void) { int rc; long reqtime, contime; char *p; - long double now; + struct timespec now; if ((rc = ParseHttpMessage(&cpm.msg, inbuf.p, amtread)) != -1) { if (!rc) return false; hdrsize = rc; @@ -6387,9 +6397,9 @@ static bool HandleMessageActual(void) { p = stpcpy(p, "\r\n"); } if (loglatency || LOGGABLE(kLogDebug) || hasonloglatency) { - now = nowl(); - reqtime = (long)((now - startrequest) * 1e6L); - contime = (long)((now - startconnection) * 1e6L); + now = _timespec_real(); + reqtime = _timespec_tomicros(_timespec_sub(now, startrequest)); + contime = _timespec_tomicros(_timespec_sub(now, startconnection)); if (hasonloglatency) LuaOnLogLatency(reqtime, contime); if (loglatency || LOGGABLE(kLogDebug)) LOGF(kLogDebug, "(stat) %`'.*s latency r: %,ldµs c: %,ldµs", @@ -6431,14 +6441,14 @@ static void HandleMessages(void) { size_t got; for (once = false;;) { InitRequest(); - startread = nowl(); + startread = _timespec_real(); for (;;) { if (!cpm.msg.i && amtread) { - startrequest = nowl(); + startrequest = _timespec_real(); if (HandleMessage()) break; } if ((rc = reader(client, inbuf.p + amtread, inbuf.n - amtread)) != -1) { - startrequest = nowl(); + startrequest = _timespec_real(); got = rc; amtread += got; if (amtread) { @@ -6496,7 +6506,10 @@ static void HandleMessages(void) { return; } if (killed || (terminated && !amtread) || - (meltdown && (!amtread || nowl() - startread > 1))) { + (meltdown && + (!amtread || + _timespec_gte(_timespec_sub(_timespec_real(), startread), + (struct timespec){1})))) { if (amtread) { LockInc(&shared->c.dropped); SendServiceUnavailable(); @@ -6754,7 +6767,7 @@ static int HandleConnection(size_t i) { clientaddrsize = sizeof(clientaddr); if ((client = accept4(servers.p[i].fd, &clientaddr, &clientaddrsize, SOCK_CLOEXEC)) != -1) { - startconnection = nowl(); + startconnection = _timespec_real(); if (UNLIKELY(maxworkers) && shared->workers >= maxworkers) { EnterMeltdownMode(); SendServiceUnavailable(); @@ -6807,8 +6820,9 @@ static int HandleConnection(size_t i) { CloseServerFds(); } HandleMessages(); - DEBUGF("(stat) %s closing after %,ldµs", DescribeClient(), - (long)((nowl() - startconnection) * 1e6L)); + DEBUGF( + "(stat) %s closing after %,ldµs", DescribeClient(), + _timespec_tomicros(_timespec_sub(_timespec_real(), startconnection))); if (!pid) { if (hasonworkerstop) { CallSimpleHook("OnWorkerStop"); @@ -7087,7 +7101,7 @@ static void HandleShutdown(void) { // this function coroutines with linenoise static int EventLoop(int ms) { - long double t; + struct timespec t; DEBUGF("(repl) event loop"); while (!terminated) { errno = 0; @@ -7105,7 +7119,9 @@ static int EventLoop(int ms) { EnterMeltdownMode(); lua_repl_unlock(); meltdown = false; - } else if ((t = nowl()) - lastheartbeat > heartbeatint / 1000.) { + } else if (_timespec_gte( + _timespec_sub((t = _timespec_real()), lastheartbeat), + heartbeatinterval)) { lastheartbeat = t; HandleHeartbeat(); } else if (HandlePoll(ms) == -1) { @@ -7341,8 +7357,9 @@ void RedBean(int argc, char *argv[]) { } reader = read; writer = WritevAll; - gmtoff = GetGmtOffset((lastrefresh = startserver = nowl())); + gmtoff = GetGmtOffset((lastrefresh = startserver = _timespec_real()).tv_sec); mainpid = getpid(); + heartbeatinterval.tv_sec = 5; CHECK_GT(CLK_TCK, 0); CHECK_NE(MAP_FAILED, (shared = mmap(NULL, ROUNDUP(sizeof(struct Shared), FRAMESIZE), @@ -7392,7 +7409,7 @@ void RedBean(int argc, char *argv[]) { close(fd); } ChangeUser(); - UpdateCurrentDate(nowl()); + UpdateCurrentDate(_timespec_real()); CollectGarbage(); hdrbuf.n = 4 * 1024; hdrbuf.p = xmalloc(hdrbuf.n); @@ -7410,12 +7427,12 @@ void RedBean(int argc, char *argv[]) { } } #ifdef STATIC - EventLoop(heartbeatint); + EventLoop(_timespec_tomillis(heartbeatinterval)); #else GetHostsTxt(); // for effect GetResolvConf(); // for effect if (daemonize || uniprocess || !linenoiseIsTerminal()) { - EventLoop(heartbeatint); + EventLoop(_timespec_tomillis(heartbeatinterval)); } else if (IsWindows()) { CHECK_NE(-1, _spawn(WindowsReplThread, 0, &replth)); EventLoop(100);