Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,10 @@ hrpc_calc_server: prepare hrpc_calc_protoc



unittest: prepare libhv
hloop_clock_test: prepare libhv
$(CC) -g -Wall -O0 -std=c99 -I. -Ibase -Issl -Ievent -o bin/hloop_clock_test unittest/hloop_clock_test.c -Llib -lhv -pthread

unittest: prepare libhv hloop_clock_test
$(CC) -g -Wall -O0 -std=c99 -I. -Ibase -o bin/rbtree_test unittest/rbtree_test.c base/rbtree.c
$(CC) -g -Wall -O0 -std=c99 -I. -Ibase -o bin/hbase_test unittest/hbase_test.c base/hbase.c
$(CC) -g -Wall -O0 -std=c99 -I. -Ibase -o bin/mkdir_p unittest/mkdir_test.c base/hbase.c
Expand Down Expand Up @@ -505,4 +508,4 @@ echo-servers:
echo-benchmark: echo-servers
bash echo-servers/benchmark.sh

.PHONY: clean prepare install uninstall libhv examples unittest evpp echo-servers
.PHONY: clean prepare install uninstall libhv examples unittest hloop_clock_test evpp echo-servers
11 changes: 8 additions & 3 deletions event/hloop.c
Original file line number Diff line number Diff line change
Expand Up @@ -553,14 +553,19 @@ hloop_status_e hloop_status(hloop_t* loop) {

void hloop_update_time(hloop_t* loop) {
loop->cur_hrtime = gethrtime_us();
if (hloop_now(loop) != time(NULL)) {
uint64_t elapsed_ms = (loop->cur_hrtime - loop->start_hrtime) / 1000;
uint64_t now_ms = loop->start_ms + elapsed_ms;
uint64_t system_ms = gettimeofday_ms();
uint64_t drift_ms = system_ms >= now_ms ? system_ms - now_ms : now_ms - system_ms;
// Compare complete timestamps: separately truncated seconds can differ without a clock change.
if (drift_ms >= 1000) {
// systemtime changed, we adjust start_ms
loop->start_ms = gettimeofday_ms() - (loop->cur_hrtime - loop->start_hrtime) / 1000;
loop->start_ms = system_ms - elapsed_ms;
}
}

uint64_t hloop_now(hloop_t* loop) {
return loop->start_ms / 1000 + (loop->cur_hrtime - loop->start_hrtime) / 1000000;
return hloop_now_ms(loop) / 1000;
}

uint64_t hloop_now_ms(hloop_t* loop) {
Expand Down
1 change: 1 addition & 0 deletions scripts/unittest.sh
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ bin/hurl_test
# bin/hthread_test
# bin/hmutex_test
bin/socketpair_test
bin/hloop_clock_test || exit $?
# bin/threadpool_test
# bin/objectpool_test
bin/sizeof_test
Expand Down
13 changes: 13 additions & 0 deletions unittest/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,18 @@ set(HTTP_JS_UNITTEST_TARGETS ${HTTP_JS_UNITTEST_TARGETS} http_js_mqtt_test)
endif()
endif()

# ------event: cached clocks and wall-clock adjustment------
add_executable(hloop_clock_test hloop_clock_test.c)
target_include_directories(hloop_clock_test PRIVATE .. ../base ../ssl ../event)
if(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND TARGET hv_static)
target_compile_definitions(hloop_clock_test PRIVATE HLOOP_CLOCK_TEST_WRAPPED)
target_link_libraries(hloop_clock_test hv_static)
set_property(TARGET hloop_clock_test APPEND_STRING PROPERTY LINK_FLAGS
" -Wl,--wrap=gethrtime_us -Wl,--wrap=gettimeofday -Wl,--wrap=time")
else()
target_link_libraries(hloop_clock_test ${HV_LIBRARIES})
endif()

# ------event: Windows epoll initialization------
if(WIN32 AND WITH_WEPOLL)
add_executable(epoll_init_test epoll_init_test.c ../event/wepoll/wepoll.c)
Expand Down Expand Up @@ -240,6 +252,7 @@ add_custom_target(unittest DEPENDS
${HTTP_LUA_UNITTEST_TARGETS}
${HTTP_JS_UNITTEST_TARGETS}
${EVENT_UNITTEST_TARGETS}
hloop_clock_test
hdns_test
hdns_benchmark
${REDIS_UNITTEST_TARGETS}
Expand Down
135 changes: 135 additions & 0 deletions unittest/hloop_clock_test.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
#include "hevent.h"

#include <inttypes.h>
#include <stdio.h>
#include <string.h>

static int failures;

/* Keep checks active in Release/NDEBUG builds. */
static void check_equal(const char *name, uint64_t actual, uint64_t expected) {
if (actual != expected) {
fprintf(stderr, "%s: got %" PRIu64 ", expected %" PRIu64 "\n", name, actual, expected);
++failures;
}
}

static void test_cached_time(void) {
static const struct {
uint64_t start_ms;
uint64_t elapsed_us;
uint64_t expected_us;
} cases[] = {
{100900, 200000, 101100000}, {100900, 99999, 100999999}, {100900, 100000, 101000000},
{100900, 1100000, 102000000}, {100000, 999999, 100999999}, {100000, 1000000, 101000000},
};
unsigned int i;
for (i = 0; i < sizeof(cases) / sizeof(cases[0]); ++i) {
hloop_t loop;
memset(&loop, 0, sizeof(loop));
loop.start_ms = cases[i].start_ms;
loop.start_hrtime = 10000000;
loop.cur_hrtime = loop.start_hrtime + cases[i].elapsed_us;
check_equal("cached seconds", hloop_now(&loop), cases[i].expected_us / 1000000);
check_equal("cached milliseconds", hloop_now_ms(&loop), cases[i].expected_us / 1000);
check_equal("cached microseconds", hloop_now_us(&loop), cases[i].expected_us);
check_equal("cached high-resolution time", hloop_now_hrtime(&loop), loop.cur_hrtime);
}
}

#ifdef HLOOP_CLOCK_TEST_WRAPPED
/* Linux static-library tests replace clock sources without changing host time. */
static uint64_t wall_ms;
static uint64_t hr_us;

unsigned long long __wrap_gethrtime_us(void) {
return hr_us;
}

int __wrap_gettimeofday(struct timeval *tv, void *tz) {
(void)tz;
tv->tv_sec = (time_t)(wall_ms / 1000);
tv->tv_usec = (long)((wall_ms % 1000) * 1000);
return 0;
}

time_t __wrap_time(time_t *out) {
time_t now = (time_t)(wall_ms / 1000);
if (out) *out = now;
return now;
}

static void test_second_boundary(void) {
hloop_t loop;
uint64_t first, second, third;
memset(&loop, 0, sizeof(loop));
loop.start_ms = 100900;
loop.start_hrtime = 10000000;

/* The wall sample follows the monotonic sample by 2 ms on the first
* update. Both clock sources then advance normally across a second. */
hr_us = loop.start_hrtime + 100000;
wall_ms = 101002;
hloop_update_time(&loop);
first = hloop_now_ms(&loop);
hr_us = loop.start_hrtime + 1099000;
wall_ms = 101999;
hloop_update_time(&loop);
second = hloop_now_ms(&loop);
hr_us = loop.start_hrtime + 1100000;
wall_ms = 102000;
hloop_update_time(&loop);
third = hloop_now_ms(&loop);

if (third < second) {
fprintf(stderr, "cached time moved backward from %" PRIu64 " to %" PRIu64 " ms\n", second, third);
++failures;
}
check_equal("first boundary sample", first, 101000);
check_equal("second boundary sample", second, 101999);
check_equal("third boundary sample", third, 102000);
check_equal("unchanged wall-clock origin", loop.start_ms, 100900);
check_equal("unchanged high-resolution origin", loop.start_hrtime, 10000000);
check_equal("high-resolution sample", hloop_now_hrtime(&loop), hr_us);
}

static void test_clock_adjustments(void) {
static const struct {
int drift_ms;
int correction_ms;
} cases[] = {{0, 0}, {1, 0}, {-1, 0}, {999, 0}, {-999, 0}, {1000, 1000}, {-1000, -1000}, {5000, 5000}, {-5000, -5000}};
unsigned int i;
for (i = 0; i < sizeof(cases) / sizeof(cases[0]); ++i) {
hloop_t loop;
uint64_t expected_ms = (uint64_t)(101100 + cases[i].correction_ms);
memset(&loop, 0, sizeof(loop));
loop.start_ms = 100900;
loop.start_hrtime = 10000000;
hr_us = loop.start_hrtime + 200123;
wall_ms = (uint64_t)(101100 + cases[i].drift_ms);
hloop_update_time(&loop);

check_equal("adjusted milliseconds", hloop_now_ms(&loop), expected_ms);
check_equal("adjusted seconds", hloop_now(&loop), expected_ms / 1000);
check_equal("adjusted microseconds", hloop_now_us(&loop), expected_ms * 1000 + 123);
check_equal("wall-clock origin adjustment", loop.start_ms, (uint64_t)(100900 + cases[i].correction_ms));
check_equal("unchanged high-resolution origin", loop.start_hrtime, 10000000);
check_equal("high-resolution sample", hloop_now_hrtime(&loop), hr_us);
}
}
#endif

int main(void) {
#ifdef HLOOP_CLOCK_TEST_WRAPPED
test_second_boundary();
test_clock_adjustments();
#endif
test_cached_time();
if (failures) return 1;
#ifdef HLOOP_CLOCK_TEST_WRAPPED
puts("hloop_clock_test passed (cached time and deterministic clock updates)");
#else
puts("hloop_clock_test passed (cached time; clock-update injection requires Linux and a static library)");
#endif
return 0;
}
Loading