From d07319f790eb38c867bd04a23de674e2393825ff Mon Sep 17 00:00:00 2001 From: Rickard Green Date: Sun, 22 Mar 2015 20:20:28 +0100 Subject: Better support for poor os monotonic sources --- .../sys/common/erl_os_monotonic_time_extender.c | 88 +++++++++ .../sys/common/erl_os_monotonic_time_extender.h | 65 +++++++ erts/emulator/sys/unix/erl_unix_sys.h | 7 +- erts/emulator/sys/unix/sys_time.c | 200 +++++++-------------- erts/emulator/sys/win32/sys_time.c | 58 ++++-- 5 files changed, 255 insertions(+), 163 deletions(-) create mode 100644 erts/emulator/sys/common/erl_os_monotonic_time_extender.c create mode 100644 erts/emulator/sys/common/erl_os_monotonic_time_extender.h (limited to 'erts/emulator/sys') diff --git a/erts/emulator/sys/common/erl_os_monotonic_time_extender.c b/erts/emulator/sys/common/erl_os_monotonic_time_extender.c new file mode 100644 index 0000000000..f3633b7267 --- /dev/null +++ b/erts/emulator/sys/common/erl_os_monotonic_time_extender.c @@ -0,0 +1,88 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2015. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif +#include "erl_os_monotonic_time_extender.h" + +#ifdef USE_THREADS + +static void *os_monotonic_time_extender(void *vstatep) +{ + ErtsOsMonotonicTimeExtendState *state = (ErtsOsMonotonicTimeExtendState *) vstatep; + long sleep_time = state->check_interval*1000; + Uint32 (*raw_os_mtime)(void) = state->raw_os_monotonic_time; + Uint32 last_msb = 0; + + while (1) { + Uint32 msb = (*raw_os_mtime)() & (((Uint32) 1) << 31); + + if (msb != last_msb) { + int ix = ((int) (last_msb >> 31)) & 1; + Uint32 xtnd = (Uint32) erts_atomic32_read_nob(&state->extend[ix]); + erts_atomic32_set_nob(&state->extend[ix], (erts_aint32_t) (xtnd + 1)); + last_msb = msb; + } + erts_milli_sleep(sleep_time); + } + + erl_exit(ERTS_ABORT_EXIT, "os_monotonic_time_extender thread terminating"); + return NULL; +} + +static erts_tid_t os_monotonic_extender_tid; +#endif + +void +erts_init_os_monotonic_time_extender(ErtsOsMonotonicTimeExtendState *statep, + Uint32 (*raw_os_monotonic_time)(void), + int check_seconds) +{ +#ifdef USE_THREADS + statep->raw_os_monotonic_time = raw_os_monotonic_time; + erts_atomic32_init_nob(&statep->extend[0], (erts_aint32_t) 0); + erts_atomic32_init_nob(&statep->extend[1], (erts_aint32_t) 0); + statep->check_interval = check_seconds; + +#else + statep->extend[0] = (Uint32) 0; + statep->extend[1] = (Uint32) 0; + statep->last_msb = (ErtsMonotonicTime) 0; +#endif +} + +void +erts_late_init_os_monotonic_time_extender(ErtsOsMonotonicTimeExtendState *statep) +{ +#ifdef USE_THREADS + erts_thr_opts_t thr_opts = ERTS_THR_OPTS_DEFAULT_INITER; + thr_opts.detached = 1; + thr_opts.suggested_stack_size = 4; + +#if 0 + thr_opts.name = "os_monotonic_time_extender"; +#endif + + erts_thr_create(&os_monotonic_extender_tid, + os_monotonic_time_extender, + (void*) statep, + &thr_opts); +#endif +} diff --git a/erts/emulator/sys/common/erl_os_monotonic_time_extender.h b/erts/emulator/sys/common/erl_os_monotonic_time_extender.h new file mode 100644 index 0000000000..0f9e7c86ae --- /dev/null +++ b/erts/emulator/sys/common/erl_os_monotonic_time_extender.h @@ -0,0 +1,65 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2015. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ + +#ifndef ERL_OS_MONOTONIC_TIME_EXTENDER_H__ +#define ERL_OS_MONOTONIC_TIME_EXTENDER_H__ + +#include "sys.h" +#include "erl_threads.h" + +typedef struct { +#ifdef USE_THREADS + Uint32 (*raw_os_monotonic_time)(void); + erts_atomic32_t extend[2]; + int check_interval; +#else + Uint32 extend[2]; + ErtsMonotonicTime last_msb; +#endif +} ErtsOsMonotonicTimeExtendState; + +#ifdef USE_THREADS +# define ERTS_CHK_EXTEND_OS_MONOTONIC_TIME(S, RT) ((void) 1) +# define ERTS_EXTEND_OS_MONOTONIC_TIME(S, RT) \ + ((((ErtsMonotonicTime) \ + erts_atomic32_read_nob(&((S)->extend[((int) ((RT) >> 31)) & 1]))) \ + << 32) \ + + (RT)) +#else +# define ERTS_CHK_EXTEND_OS_MONOTONIC_TIME(S, RT) \ + do { \ + Uint32 msb__ = (RT) & (((Uint32) 1) << 31); \ + if (msb__ != (S)->last_msb) { \ + int ix__ = ((int) ((S)->last_msb >> 31)) & 1; \ + (S)->extend[ix__]++; \ + (S)->last_msb = msb; \ + } \ + } while (0) +# define ERTS_EXTEND_OS_MONOTONIC_TIME(S, RT) \ + ((((ErtsMonotonicTime) (S)->extend[((int) ((RT) >> 31)) & 1]) << 32) + (RT)) +#endif + +void +erts_init_os_monotonic_time_extender(ErtsOsMonotonicTimeExtendState *statep, + Uint32 (*raw_os_monotonic_time)(void), + int check_seconds); +void +erts_late_init_os_monotonic_time_extender(ErtsOsMonotonicTimeExtendState *statep); + +#endif diff --git a/erts/emulator/sys/unix/erl_unix_sys.h b/erts/emulator/sys/unix/erl_unix_sys.h index 5417bb2687..303ebeee7c 100644 --- a/erts/emulator/sys/unix/erl_unix_sys.h +++ b/erts/emulator/sys/unix/erl_unix_sys.h @@ -206,12 +206,9 @@ ErtsMonotonicTime erts_os_monotonic_time(void); || defined(OS_MONOTONIC_TIME_USING_TIMES) #if defined(OS_MONOTONIC_TIME_USING_TIMES) +/* Time unit determined at runtime... */ # undef ERTS_COMPILE_TIME_MONOTONIC_TIME_UNIT -# define ERTS_COMPILE_TIME_MONOTONIC_TIME_UNIT (1000*1000) -# define ERTS_HAVE_ERTS_OS_TIME_OFFSET_FINALIZE 1 -void erts_os_time_offset_finalize(void); -# define ERTS_HAVE_ERTS_OS_MONOTONIC_TIME_INIT -void erts_os_monotonic_time_init(void); +# define ERTS_COMPILE_TIME_MONOTONIC_TIME_UNIT 0 #endif ErtsMonotonicTime erts_os_monotonic_time(void); diff --git a/erts/emulator/sys/unix/sys_time.c b/erts/emulator/sys/unix/sys_time.c index 9fdb1930b7..9db727b111 100644 --- a/erts/emulator/sys/unix/sys_time.c +++ b/erts/emulator/sys/unix/sys_time.c @@ -33,6 +33,7 @@ #include "sys.h" #include "global.h" +#include "erl_os_monotonic_time_extender.h" #ifdef NO_SYSCONF # define TICKS_PER_SEC() HZ @@ -55,36 +56,19 @@ #undef ERTS_SYS_TIME_INTERNAL_STATE_WRITE_FREQ__ #undef ERTS_SYS_TIME_INTERNAL_STATE_READ_ONLY__ +#undef ERTS_SYS_TIME_INTERNAL_STATE_READ_MOSTLY__ #if defined(OS_MONOTONIC_TIME_USING_TIMES) -#define ERTS_WRAP_SYS_TIMES 1 -#define ERTS_SYS_TIME_INTERNAL_STATE_WRITE_FREQ__ -#define ERTS_SYS_TIME_INTERNAL_STATE_READ_ONLY__ - -/* - * Not sure there is a need to use times() anymore, perhaps drop - * support for this soon... - * - * sys_times() might need to be wrapped and the values shifted (right) - * a bit to cope with faster ticks, this has to be taken care - * of dynamically to start with, a special version that uses - * the times() return value as a high resolution timer can be made - * to fully utilize the faster ticks, like on windows, but for now, we'll - * settle with this silly workaround - */ -#ifdef ERTS_WRAP_SYS_TIMES -static clock_t sys_times_wrap(void); -#define KERNEL_TICKS() (sys_times_wrap() & \ - ((1UL << ((sizeof(clock_t) * 8) - 1)) - 1)) -#define ERTS_KERNEL_TICK_TO_USEC(TCKS) (((TCKS)*(1000*1000)) \ - / internal_state.r.o.ticks_per_sec_wrap) -#else +static Uint32 +get_tick_count(void) +{ + struct tms unused; + return (Uint32) times(&unused); +} -#define KERNEL_TICKS() (sys_times(&internal_state.w.f.dummy_tms) & \ - ((1UL << ((sizeof(clock_t) * 8) - 1)) - 1)) -#define ERTS_KERNEL_TICK_TO_USEC(TCKS) (((TCKS)*(1000*1000))/SYS_CLK_TCK) -#endif +#define ERTS_SYS_TIME_INTERNAL_STATE_READ_ONLY__ +#define ERTS_SYS_TIME_INTERNAL_STATE_READ_MOSTLY__ #endif @@ -109,8 +93,15 @@ ErtsMonotonicTime clock_gettime_monotonic_verified(void); #ifdef ERTS_SYS_TIME_INTERNAL_STATE_READ_ONLY__ struct sys_time_internal_state_read_only__ { #if defined(OS_MONOTONIC_TIME_USING_TIMES) - int ticks_bsr; - int ticks_per_sec_wrap; + int times_shift; +#endif +}; +#endif + +#ifdef ERTS_SYS_TIME_INTERNAL_STATE_READ_MOSTLY__ +struct sys_time_internal_state_read_mostly__ { +#if defined(OS_MONOTONIC_TIME_USING_TIMES) + ErtsOsMonotonicTimeExtendState os_mtime_xtnd; #endif }; #endif @@ -121,15 +112,6 @@ struct sys_time_internal_state_write_freq__ { #if defined(__linux__) && defined(OS_MONOTONIC_TIME_USING_CLOCK_GETTIME) ErtsMonotonicTime last_delivered; #endif -#if defined(OS_MONOTONIC_TIME_USING_TIMES) - ErtsMonotonicTime last_tick_count; - ErtsMonotonicTime last_tick_wrap_count; - ErtsMonotonicTime last_tick_monotonic_time; - ErtsMonotonicTime last_timeofday_usec; -#ifndef ERTS_WRAP_SYS_TIMES - SysTimes dummy_tms; -#endif -#endif }; #endif @@ -144,6 +126,14 @@ static struct { * ASSUMED_CACHE_LINE_SIZE]; } r; #endif +#ifdef ERTS_SYS_TIME_INTERNAL_STATE_READ_MOSTLY__ + union { + struct sys_time_internal_state_read_mostly__ m; + char align__[(((sizeof(struct sys_time_internal_state_read_mostly__) - 1) + / ASSUMED_CACHE_LINE_SIZE) + 1) + * ASSUMED_CACHE_LINE_SIZE]; + } wr; +#endif #ifdef ERTS_SYS_TIME_INTERNAL_STATE_WRITE_FREQ__ union { struct sys_time_internal_state_write_freq__ f; @@ -193,8 +183,6 @@ sys_init_time(ErtsSysInitTimeResult *init_resp) init_resp->os_monotonic_info.func = "gethrtime"; #elif defined(OS_MONOTONIC_TIME_USING_TIMES) init_resp->os_monotonic_info.func = "times"; - init_resp->os_monotonic_info.locked_use = 1; - init_resp->os_monotonic_info.resolution = TICKS_PER_SEC(); #else # error Unknown erts_os_monotonic_time() implementation #endif @@ -247,7 +235,9 @@ sys_init_time(ErtsSysInitTimeResult *init_resp) #endif /* defined(ERTS_HAVE_OS_MONOTONIC_TIME_SUPPORT) */ +#ifdef ERTS_COMPILE_TIME_MONOTONIC_TIME_UNIT init_resp->os_monotonic_time_unit = ERTS_COMPILE_TIME_MONOTONIC_TIME_UNIT; +#endif init_resp->sys_clock_resolution = SYS_CLOCK_RESOLUTION; /* @@ -258,38 +248,40 @@ sys_init_time(ErtsSysInitTimeResult *init_resp) erl_exit(ERTS_ABORT_EXIT, "Can't get clock ticks/sec\n"); #if defined(OS_MONOTONIC_TIME_USING_TIMES) +#if ERTS_COMPILE_TIME_MONOTONIC_TIME_UNIT +# error Time unit is supposed to be determined at runtime... +#endif + { + ErtsMonotonicTime resolution = erts_sys_time_data__.r.o.ticks_per_sec; + ErtsMonotonicTime time_unit = resolution; + int shift = 0; - if (erts_sys_time_data__.r.o.ticks_per_sec >= 1000) { - /* Workaround for beta linux kernels, need to be done in runtime - to make erlang run on both 2.4 and 2.5 kernels. In the future, - the kernel ticks might as - well be used as a high res timer instead, but that's for when the - majority uses kernels with HZ == 1024 */ - internal_state.r.o.ticks_bsr = 3; - } else { - internal_state.r.o.ticks_bsr = 0; - } + while (time_unit < 1000*1000) { + time_unit <<= 1; + shift++; + } - internal_state.r.o.ticks_per_sec_wrap - = (erts_sys_time_data__.r.o.ticks_per_sec - >> internal_state.r.o.ticks_bsr); + init_resp->os_monotonic_info.resolution = resolution; + init_resp->os_monotonic_time_unit = time_unit; + init_resp->os_monotonic_info.extended = 1; + internal_state.r.o.times_shift = shift; - erts_smp_mtx_init(&internal_state.w.f.mtx, "os_monotonic_time"); - internal_state.w.f.last_tick_count = KERNEL_TICKS(); - internal_state.w.f.last_tick_wrap_count = 0; - internal_state.w.f.last_tick_monotonic_time - = ERTS_KERNEL_TICK_TO_USEC(internal_state.w.f.last_tick_count); - { - SysTimeval tv; - sys_gettimeofday(&tv); - internal_state.w.f.last_timeofday_usec = tv.tv_sec*(1000*1000); - internal_state.w.f.last_timeofday_usec += tv.tv_usec; + erts_init_os_monotonic_time_extender(&internal_state.wr.m.os_mtime_xtnd, + get_tick_count, + (1 << 29) / resolution); } - #endif /* defined(OS_MONOTONIC_TIME_USING_TIMES) */ } +void +erts_late_sys_init_time(void) +{ +#if defined(OS_MONOTONIC_TIME_USING_TIMES) + erts_late_init_os_monotonic_time_extender(&internal_state.wr.m.os_mtime_xtnd); +#endif +} + #if defined(OS_MONOTONIC_TIME_USING_CLOCK_GETTIME) static ERTS_INLINE ErtsMonotonicTime @@ -379,86 +371,14 @@ ErtsMonotonicTime erts_os_monotonic_time(void) #elif defined(OS_MONOTONIC_TIME_USING_TIMES) -static clock_t sys_times_wrap(void) -{ - SysTimes dummy; - clock_t result = (sys_times(&dummy) >> internal_state.r.o.ticks_bsr); - return result; -} - -void -erts_os_time_offset_finalize(void) -{ - erts_smp_mtx_lock(&internal_state.w.f.mtx); - internal_state.w.f.last_tick_wrap_count = 0; - erts_smp_mtx_unlock(&internal_state.w.f.mtx); -} - -#define ERTS_TIME_EXCEED_TICK_LIMIT(SYS_TIME, TCK_TIME) \ - (((Uint64) (SYS_TIME)) - (((Uint64) (TCK_TIME)) \ - - ERTS_KERNEL_TICK_TO_USEC(1)) \ - > ERTS_KERNEL_TICK_TO_USEC(2)) - -/* Returns monotonic time in micro seconds */ ErtsMonotonicTime erts_os_monotonic_time(void) { - SysTimeval tv; - ErtsMonotonicTime res; - ErtsMonotonicTime tick_count; - ErtsMonotonicTime tick_count_usec; - ErtsMonotonicTime tick_monotonic_time; - ErtsMonotonicTime timeofday_usec; - ErtsMonotonicTime timeofday_diff_usec; - - erts_smp_mtx_lock(&internal_state.w.f.mtx); - - tick_count = (ErtsMonotonicTime) KERNEL_TICKS(); - sys_gettimeofday(&tv); - - if (internal_state.w.f.last_tick_count > tick_count) { - internal_state.w.f.last_tick_wrap_count - += (((ErtsMonotonicTime) 1) << ((sizeof(clock_t) * 8) - 1)); - } - internal_state.w.f.last_tick_count = tick_count; - tick_count += internal_state.w.f.last_tick_wrap_count; - - tick_count_usec = ERTS_KERNEL_TICK_TO_USEC(tick_count); - - timeofday_usec = (ErtsMonotonicTime) tv.tv_sec*(1000*1000); - timeofday_usec += (ErtsMonotonicTime) tv.tv_usec; - timeofday_diff_usec = timeofday_usec; - timeofday_diff_usec -= internal_state.w.f.last_timeofday_usec; - internal_state.w.f.last_timeofday_usec = timeofday_usec; - - if (timeofday_diff_usec < 0) { - /* timeofday jumped backwards use tick count only... */ - tick_monotonic_time = tick_count_usec; - } - else { - /* Use time diff from of timeofday if not off by too much... */ - tick_monotonic_time = internal_state.w.f.last_tick_monotonic_time; - tick_monotonic_time += timeofday_diff_usec; - - if (ERTS_TIME_EXCEED_TICK_LIMIT(tick_monotonic_time, tick_count_usec)) { - /* - * Value off by more than one tick from tick_count, i.e. - * timofday leaped one way or the other. We use - * tick_count_usec as is instead and unfortunately - * get lousy precision. - */ - tick_monotonic_time = tick_count_usec; - } - } - - if (internal_state.w.f.last_tick_monotonic_time < tick_monotonic_time) - internal_state.w.f.last_tick_monotonic_time = tick_monotonic_time; - - res = internal_state.w.f.last_tick_monotonic_time; - - erts_smp_mtx_unlock(&internal_state.w.f.mtx); - - return res; + Uint32 ticks = get_tick_count(); + ERTS_CHK_EXTEND_OS_MONOTONIC_TIME(&internal_state.wr.m.os_mtime_xtnd, + ticks); + return ERTS_EXTEND_OS_MONOTONIC_TIME(&internal_state.wr.m.os_mtime_xtnd, + ticks) << internal_state.r.o.times_shift; } #endif /* !defined(OS_MONOTONIC_TIME_USING_TIMES) */ diff --git a/erts/emulator/sys/win32/sys_time.c b/erts/emulator/sys/win32/sys_time.c index 3a10125c81..5fa575fa3b 100644 --- a/erts/emulator/sys/win32/sys_time.c +++ b/erts/emulator/sys/win32/sys_time.c @@ -25,6 +25,7 @@ #endif #include "sys.h" #include "assert.h" +#include "erl_os_monotonic_time_extender.h" #define LL_LITERAL(X) ERTS_I64_LITERAL(X) @@ -80,6 +81,10 @@ struct sys_time_internal_state_read_only__ { BOOL (WINAPI *pQueryPerformanceCounter)(LARGE_INTEGER *); }; +struct sys_time_internal_state_read_mostly__ { + ErtsOsMonotonicTimeExtendState os_mtime_xtnd; +}; + struct sys_time_internal_state_write_freq__ { erts_smp_mtx_t mtime_mtx; ULONGLONG wrap; @@ -93,6 +98,12 @@ __declspec(align(ASSUMED_CACHE_LINE_SIZE)) struct { / ASSUMED_CACHE_LINE_SIZE) + 1) * ASSUMED_CACHE_LINE_SIZE]; } r; + union { + struct sys_time_internal_state_read_mostly__ m; + char align__[(((sizeof(struct sys_time_internal_state_read_mostly__) - 1) + / ASSUMED_CACHE_LINE_SIZE) + 1) + * ASSUMED_CACHE_LINE_SIZE]; + } wr; union { struct sys_time_internal_state_write_freq__ f; char align__[(((sizeof(struct sys_time_internal_state_write_freq__) - 1) @@ -114,29 +125,27 @@ os_monotonic_time_qpc(void) return (ErtsMonotonicTime) pc.QuadPart; } +static Uint32 +get_tick_count(void) +{ + return (Uint32) GetTickCount(); +} + static ErtsMonotonicTime os_monotonic_time_gtc32(void) { - ULONGLONG res, ticks; - - erts_smp_mtx_lock(&internal_state.w.f.mtime_mtx); - - ticks = (ULONGLONG) (GetTickCount() & 0x7FFFFFFF); - if (ticks < internal_state.w.f.last_tick_count) - internal_state.w.f.wrap += (ULONGLONG) LL_LITERAL(1) << 31; - internal_state.w.f.last_tick_count = ticks; - res = ticks + internal_state.w.f.wrap; - - erts_smp_mtx_unlock(&internal_state.w.f.mtime_mtx); - - return (ErtsMonotonicTime) res*1000; + Uint32 ticks = (Uint32) GetTickCount(); + ERTS_CHK_EXTEND_OS_MONOTONIC_TIME(&internal_state.wr.m.os_mtime_xtnd, + tick_count); + return ERTS_EXTEND_OS_MONOTONIC_TIME(&internal_state.wr.m.os_mtime_xtnd, + ticks) << 10; } static ErtsMonotonicTime os_monotonic_time_gtc64(void) { ULONGLONG ticks = (*internal_state.r.o.pGetTickCount64)(); - return (ErtsMonotonicTime) ticks*1000; + return (ErtsMonotonicTime) ticks << 10; } /* @@ -163,9 +172,14 @@ sys_init_time(ErtsSysInitTimeResult *init_resp) init_resp->os_monotonic_info.func = "GetTickCount"; init_resp->os_monotonic_info.locked_use = 1; - init_resp->os_monotonic_info.resolution = 1000; - time_unit = (ErtsMonotonicTime) 1000*1000; + /* 10-16 ms resolution according to MicroSoft documentation */ + init_resp->os_monotonic_info.resolution = 100; /* 10 ms */ + time_unit = (ErtsMonotonicTime) (1000 << 10); os_mtime_func = os_monotonic_time_gtc32; + init_resp->os_monotonic_info.extended = 1; + erts_init_os_monotonic_time_extender(&internal_state.wr.m.os_mtime_xtnd, + get_tick_count, + 60*60*24*7); /* Check once a week */ } else { int major, minor, build; @@ -184,8 +198,9 @@ sys_init_time(ErtsSysInitTimeResult *init_resp) init_resp->os_monotonic_info.func = "GetTickCount64"; init_resp->os_monotonic_info.locked_use = 0; - init_resp->os_monotonic_info.resolution = 1000; - time_unit = (ErtsMonotonicTime) 1000*1000; + /* 10-16 ms resolution according to MicroSoft documentation */ + init_resp->os_monotonic_info.resolution = 100; /* 10 ms */ + time_unit = (ErtsMonotonicTime) (1000 << 10); os_mtime_func = os_monotonic_time_gtc64; } else { /* Vista or newer... */ @@ -235,6 +250,13 @@ sys_init_time(ErtsSysInitTimeResult *init_resp) } +void +erts_late_sys_init_time(void) +{ + if (erts_sys_time_data__.r.o.os_monotonic_time == os_monotonic_time_gtc32) + erts_late_init_os_monotonic_time_extender(&internal_state.wr.m.os_mtime_xtnd); +} + /* Returns a switchtimes for DST as UTC filetimes given data from a TIME_ZONE_INFORMATION, see sys_localtime_r for usage. */ static void -- cgit v1.2.3