From e7d78462fe38e7390f049b0d8794bc1ca301f93b Mon Sep 17 00:00:00 2001 From: Jacob Su Date: Tue, 15 Oct 2024 17:52:17 +0800 Subject: [PATCH] ST: Use clock_gettime to prevent time jumping backwards. v7.0.17 (#3979) try to fix #3978 **Background** check #3978 **Research** I referred the Android platform's solution, because I have android background, and there is a loop to handle message inside android. https://github.com/aosp-mirror/platform_frameworks_base/blob/ff007a03c01bf936d1e961a13adff9f266d5189c/core/java/android/os/Handler.java#L701-L706C6 ``` public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) { if (delayMillis < 0) { delayMillis = 0; } return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); } ``` https://github.com/aosp-mirror/platform_system_core/blob/59d9dc1f50b1ae8630ec11a431858a3cb66487b7/libutils/SystemClock.cpp#L37-L51 ``` /* * native public static long uptimeMillis(); */ int64_t uptimeMillis() { return nanoseconds_to_milliseconds(uptimeNanos()); } /* * public static native long uptimeNanos(); */ int64_t uptimeNanos() { return systemTime(SYSTEM_TIME_MONOTONIC); } ``` https://github.com/aosp-mirror/platform_system_core/blob/59d9dc1f50b1ae8630ec11a431858a3cb66487b7/libutils/Timers.cpp#L32-L55 ``` #if defined(__linux__) nsecs_t systemTime(int clock) { checkClockId(clock); static constexpr clockid_t clocks[] = {CLOCK_REALTIME, CLOCK_MONOTONIC, CLOCK_PROCESS_CPUTIME_ID, CLOCK_THREAD_CPUTIME_ID, CLOCK_BOOTTIME}; static_assert(clock_id_max == arraysize(clocks)); timespec t = {}; clock_gettime(clocks[clock], &t); return nsecs_t(t.tv_sec)*1000000000LL + t.tv_nsec; } #else nsecs_t systemTime(int clock) { // TODO: is this ever called with anything but REALTIME on mac/windows? checkClockId(clock); // Clock support varies widely across hosts. Mac OS doesn't support // CLOCK_BOOTTIME (and doesn't even have clock_gettime until 10.12). // Windows is windows. timeval t = {}; gettimeofday(&t, nullptr); return nsecs_t(t.tv_sec)*1000000000LL + nsecs_t(t.tv_usec)*1000LL; } #endif ``` For Linux system, we can use `clock_gettime` api, but it's first appeared in Mac OSX 10.12. `man clock_gettime` The requirement is to find an alternative way to get the timestamp in microsecond unit, but the `clock_gettime` get nanoseconds, the math formula is the nanoseconds / 1000 = microsecond. Then I check the performance of this api + math division. I used those code to check the `clock_gettime` performance. ``` #include #include #include #include int main() { struct timeval tv; struct timespec ts; clock_t start; clock_t end; long t; while (1) { start = clock(); gettimeofday(&tv, NULL); end = clock(); printf("gettimeofday clock is %lu\n", end - start); printf("gettimeofday is %lld\n", (tv.tv_sec * 1000000LL + tv.tv_usec)); start = clock(); clock_gettime(CLOCK_MONOTONIC, &ts); t = ts.tv_sec * 1000000L + ts.tv_nsec / 1000L; end = clock(); printf("clock_monotonic clock is %lu\n", end - start); printf("clock_monotonic: seconds is %ld, nanoseconds is %ld, sum is %ld\n", ts.tv_sec, ts.tv_nsec, t); start = clock(); clock_gettime(CLOCK_MONOTONIC_RAW, &ts); t = ts.tv_sec * 1000000L + ts.tv_nsec / 1000L; end = clock(); printf("clock_monotonic_raw clock is %lu\n", end - start); printf("clock_monotonic_raw: nanoseconds is %ld, sum is %ld\n", ts.tv_nsec, t); sleep(3); } return 0; } ``` Here is output: env: Mac OS M2 chip. ``` gettimeofday clock is 11 gettimeofday is 1709775727153949 clock_monotonic clock is 2 clock_monotonic: seconds is 1525204, nanoseconds is 409453000, sum is 1525204409453 clock_monotonic_raw clock is 2 clock_monotonic_raw: nanoseconds is 770493000, sum is 1525222770493 ``` We can see the `clock_gettime` is faster than `gettimeofday`, so there are no performance risks. **MacOS solution** `clock_gettime` api only available until mac os 10.12, for the mac os older than 10.12, just keep the `gettimeofday`. check osx version in `auto/options.sh`, then add MACRO in `auto/depends.sh`, the MACRO is `MD_OSX_HAS_NO_CLOCK_GETTIME`. **CYGWIN** According to google search, it seems the `clock_gettime(CLOCK_MONOTONIC)` is not support well at least 10 years ago, but I didn't own an windows machine, so can't verify it. so keep win's solution. --------- Co-authored-by: winlin --- trunk/3rdparty/st-srs/Makefile | 5 +- trunk/3rdparty/st-srs/md.h | 31 ++++--- trunk/auto/depends.sh | 3 + trunk/auto/options.sh | 6 +- trunk/configure | 3 +- trunk/doc/CHANGELOG.md | 1 + trunk/src/core/srs_core_version7.hpp | 2 +- trunk/src/utest/srs_utest_st.cpp | 117 +++++++++++++++++++++++++++ trunk/src/utest/srs_utest_st.hpp | 15 ++++ 9 files changed, 168 insertions(+), 15 deletions(-) create mode 100644 trunk/src/utest/srs_utest_st.cpp create mode 100644 trunk/src/utest/srs_utest_st.hpp diff --git a/trunk/3rdparty/st-srs/Makefile b/trunk/3rdparty/st-srs/Makefile index 11fdc95c9..8fb418c57 100644 --- a/trunk/3rdparty/st-srs/Makefile +++ b/trunk/3rdparty/st-srs/Makefile @@ -185,11 +185,9 @@ endif # make EXTRA_CFLAGS=-UMD_HAVE_EPOLL # # or to enable sendmmsg(2) support: -# # make EXTRA_CFLAGS="-DMD_HAVE_SENDMMSG -D_GNU_SOURCE" # # or to enable stats for ST: -# # make EXTRA_CFLAGS=-DDEBUG_STATS # # or cache the stack and reuse it: @@ -201,6 +199,9 @@ endif # or enable support for asan: # make EXTRA_CFLAGS="-DMD_ASAN -fsanitize=address -fno-omit-frame-pointer" # +# or to disable the clock_gettime for MacOS before 10.12, see https://github.com/ossrs/srs/issues/3978 +# make EXTRA_CFLAGS=-DMD_OSX_NO_CLOCK_GETTIME +# # or enable the coverage for utest: # make UTEST_FLAGS="-fprofile-arcs -ftest-coverage" # diff --git a/trunk/3rdparty/st-srs/md.h b/trunk/3rdparty/st-srs/md.h index 677d6fb46..a25c0087a 100644 --- a/trunk/3rdparty/st-srs/md.h +++ b/trunk/3rdparty/st-srs/md.h @@ -101,10 +101,21 @@ extern void _st_md_cxt_restore(_st_jmp_buf_t env, int val); #error Unknown CPU architecture #endif - #define MD_GET_UTIME() \ - struct timeval tv; \ - (void) gettimeofday(&tv, NULL); \ - return (tv.tv_sec * 1000000LL + tv.tv_usec) + #if defined (MD_OSX_NO_CLOCK_GETTIME) + #define MD_GET_UTIME() \ + struct timeval tv; \ + (void) gettimeofday(&tv, NULL); \ + return (tv.tv_sec * 1000000LL + tv.tv_usec) + #else + /* + * https://github.com/ossrs/srs/issues/3978 + * use clock_gettime to get the timestamp in microseconds. + */ + #define MD_GET_UTIME() \ + struct timespec ts; \ + clock_gettime(CLOCK_MONOTONIC, &ts); \ + return (ts.tv_sec * 1000000LL + ts.tv_nsec / 1000) + #endif #elif defined (LINUX) @@ -120,13 +131,13 @@ extern void _st_md_cxt_restore(_st_jmp_buf_t env, int val); #define MD_HAVE_SOCKLEN_T /* - * All architectures and flavors of linux have the gettimeofday - * function but if you know of a faster way, use it. + * https://github.com/ossrs/srs/issues/3978 + * use clock_gettime to get the timestamp in microseconds. */ - #define MD_GET_UTIME() \ - struct timeval tv; \ - (void) gettimeofday(&tv, NULL); \ - return (tv.tv_sec * 1000000LL + tv.tv_usec) + #define MD_GET_UTIME() \ + struct timespec ts; \ + clock_gettime(CLOCK_MONOTONIC, &ts); \ + return (ts.tv_sec * 1000000LL + ts.tv_nsec / 1000) #if defined(__i386__) #define MD_GET_SP(_t) *((long *)&((_t)->context[0].__jmpbuf[4])) diff --git a/trunk/auto/depends.sh b/trunk/auto/depends.sh index a73b6b8b5..349feec2f 100755 --- a/trunk/auto/depends.sh +++ b/trunk/auto/depends.sh @@ -266,6 +266,9 @@ fi # for osx, use darwin for st, donot use epoll. if [[ $SRS_OSX == YES ]]; then _ST_MAKE=darwin-debug && _ST_OBJ="DARWIN_`uname -r`_DBG" + if [[ $SRS_OSX_HAS_CLOCK_GETTIME != YES ]]; then + _ST_EXTRA_CFLAGS="$_ST_EXTRA_CFLAGS -DMD_OSX_NO_CLOCK_GETTIME" + fi fi # for windows/cygwin if [[ $SRS_CYGWIN64 = YES ]]; then diff --git a/trunk/auto/options.sh b/trunk/auto/options.sh index 03d58c54c..e5dcfeda9 100755 --- a/trunk/auto/options.sh +++ b/trunk/auto/options.sh @@ -112,6 +112,8 @@ SRS_CROSS_BUILD_HOST= SRS_CROSS_BUILD_PREFIX= # For cache build SRS_BUILD_CACHE=YES +# Only support MacOS 10.12+ for clock_gettime, see https://github.com/ossrs/srs/issues/3978 +SRS_OSX_HAS_CLOCK_GETTIME=YES # ##################################################################################### # Toolchain for cross-build on Ubuntu for ARM or MIPS. @@ -150,7 +152,9 @@ function apply_system_options() { OS_IS_RISCV=$(gcc -dM -E - /dev/null || echo 1); fi diff --git a/trunk/configure b/trunk/configure index b9475493e..b7f004059 100755 --- a/trunk/configure +++ b/trunk/configure @@ -464,7 +464,8 @@ if [[ $SRS_UTEST == YES ]]; then MODULE_FILES=("srs_utest" "srs_utest_amf0" "srs_utest_kernel" "srs_utest_core" "srs_utest_config" "srs_utest_rtmp" "srs_utest_http" "srs_utest_avc" "srs_utest_reload" "srs_utest_mp4" "srs_utest_service" "srs_utest_app" "srs_utest_rtc" "srs_utest_config2" - "srs_utest_protocol" "srs_utest_protocol2" "srs_utest_kernel2" "srs_utest_protocol3") + "srs_utest_protocol" "srs_utest_protocol2" "srs_utest_kernel2" "srs_utest_protocol3" + "srs_utest_st") if [[ $SRS_SRT == YES ]]; then MODULE_FILES+=("srs_utest_srt") fi diff --git a/trunk/doc/CHANGELOG.md b/trunk/doc/CHANGELOG.md index 9e676930f..18a7b36a4 100644 --- a/trunk/doc/CHANGELOG.md +++ b/trunk/doc/CHANGELOG.md @@ -7,6 +7,7 @@ The changelog for SRS. ## SRS 7.0 Changelog +* v7.0, 2024-10-15, Merge [#3979](https://github.com/ossrs/srs/pull/3979): ST: Use clock_gettime to prevent time jumping backwards. v7.0.17 (#3979) * v7.0, 2024-09-09, Merge [#4158](https://github.com/ossrs/srs/pull/4158): Proxy: Support proxy server for SRS. v7.0.16 (#4158) * v7.0, 2024-09-09, Merge [#4171](https://github.com/ossrs/srs/pull/4171): Heartbeat: Report ports for proxy server. v7.0.15 (#4171) * v7.0, 2024-09-01, Merge [#4165](https://github.com/ossrs/srs/pull/4165): FLV: Refine source and http handler. v7.0.14 (#4165) diff --git a/trunk/src/core/srs_core_version7.hpp b/trunk/src/core/srs_core_version7.hpp index 458a6c3d8..67461fab9 100644 --- a/trunk/src/core/srs_core_version7.hpp +++ b/trunk/src/core/srs_core_version7.hpp @@ -9,6 +9,6 @@ #define VERSION_MAJOR 7 #define VERSION_MINOR 0 -#define VERSION_REVISION 16 +#define VERSION_REVISION 17 #endif \ No newline at end of file diff --git a/trunk/src/utest/srs_utest_st.cpp b/trunk/src/utest/srs_utest_st.cpp new file mode 100644 index 000000000..553bc0c95 --- /dev/null +++ b/trunk/src/utest/srs_utest_st.cpp @@ -0,0 +1,117 @@ +// +// Copyright (c) 2013-2024 The SRS Authors +// +// SPDX-License-Identifier: MIT +// +#include +#include +#include +#include + +using namespace std; + +VOID TEST(StTest, StUtimeInMicroseconds) +{ + st_utime_t st_time_1 = st_utime(); + // sleep 1 microsecond +#if !defined(SRS_CYGWIN64) + usleep(1); +#endif + st_utime_t st_time_2 = st_utime(); + + EXPECT_GT(st_time_1, 0); + EXPECT_GT(st_time_2, 0); + EXPECT_GE(st_time_2, st_time_1); + // st_time_2 - st_time_1 should be in range of [1, 100] microseconds + EXPECT_GE(st_time_2 - st_time_1, 0); + EXPECT_LE(st_time_2 - st_time_1, 100); +} + +static inline st_utime_t time_gettimeofday() { + struct timeval tv; + gettimeofday(&tv, NULL); + return (tv.tv_sec * 1000000LL + tv.tv_usec); +} + +VOID TEST(StTest, StUtimePerformance) +{ + clock_t start; + int gettimeofday_elapsed_time = 0; + int st_utime_elapsed_time = 0; + + // Both the st_utime(clock_gettime or gettimeofday) and gettimeofday's + // elpased time to execute is dependence on whether it is the first time be called. + // In general, the gettimeofday has better performance, but the gap between + // them is really small, maybe less than 10 clock ~ 10 microseconds. + + // check st_utime first, then gettimeofday + { + start = clock(); + st_utime_t t2 = st_utime(); + int elapsed_time = clock() - start; + st_utime_elapsed_time += elapsed_time; + EXPECT_GT(t2, 0); + + start = clock(); + st_utime_t t1 = time_gettimeofday(); + elapsed_time = clock() - start; + gettimeofday_elapsed_time += elapsed_time; + EXPECT_GT(t1, 0); + + + EXPECT_GE(gettimeofday_elapsed_time, 0); + EXPECT_GE(st_utime_elapsed_time, 0); + + // pass the test, if + EXPECT_LT(gettimeofday_elapsed_time > st_utime_elapsed_time ? + gettimeofday_elapsed_time - st_utime_elapsed_time : + st_utime_elapsed_time - gettimeofday_elapsed_time, 10); + } + + // check gettimeofday first, then st_utime + { + start = clock(); + st_utime_t t1 = time_gettimeofday(); + int elapsed_time = clock() - start; + gettimeofday_elapsed_time += elapsed_time; + EXPECT_GT(t1, 0); + + start = clock(); + st_utime_t t2 = st_utime(); + elapsed_time = clock() - start; + st_utime_elapsed_time += elapsed_time; + EXPECT_GT(t2, 0); + + EXPECT_GE(gettimeofday_elapsed_time, 0); + EXPECT_GE(st_utime_elapsed_time, 0); + + EXPECT_LT(gettimeofday_elapsed_time > st_utime_elapsed_time ? + gettimeofday_elapsed_time - st_utime_elapsed_time : + st_utime_elapsed_time - gettimeofday_elapsed_time, 10); + } + + // compare st_utime & gettimeofday in a loop + for (int i = 0; i < 100; i++) { + start = clock(); + st_utime_t t2 = st_utime(); + int elapsed_time = clock() - start; + st_utime_elapsed_time = elapsed_time; + EXPECT_GT(t2, 0); + usleep(1); + + start = clock(); + st_utime_t t1 = time_gettimeofday(); + elapsed_time = clock() - start; + gettimeofday_elapsed_time = elapsed_time; + EXPECT_GT(t1, 0); + usleep(1); + + EXPECT_GE(gettimeofday_elapsed_time, 0); + EXPECT_GE(st_utime_elapsed_time, 0); + + EXPECT_LT(gettimeofday_elapsed_time > st_utime_elapsed_time ? + gettimeofday_elapsed_time - st_utime_elapsed_time : + st_utime_elapsed_time - gettimeofday_elapsed_time, 10); + + } +} diff --git a/trunk/src/utest/srs_utest_st.hpp b/trunk/src/utest/srs_utest_st.hpp new file mode 100644 index 000000000..32862447a --- /dev/null +++ b/trunk/src/utest/srs_utest_st.hpp @@ -0,0 +1,15 @@ +// +// Copyright (c) 2013-2024 The SRS Authors +// +// SPDX-License-Identifier: MIT +// + +#ifndef SRS_UTEST_ST_HPP +#define SRS_UTEST_ST_HPP + +#include + +#include + +#endif // SRS_UTEST_ST_HPP +