Skip to content

Fix case 1044454. Stopwatch on Android does not update. #1236

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 28, 2019
Merged
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
95 changes: 77 additions & 18 deletions mono/utils/mono-time.c
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,77 @@ mono_100ns_datetime (void)
/* a made up uptime of 300 seconds */
#define MADEUP_BOOT_TIME (300 * MTICKS_PER_SEC)

#if defined(ANDROID)
#include <math.h>
/* CLOCK_MONOTONIC is the most reliable clock type on Android. However, it
does not tick when the device is sleeping (case 867885, case 1037712).
CLOCK_BOOTTIME includes that time, but is very unreliable. Some older

devices had this time ticking back or jumping back and forth (case 970945)
To fix this issue we combine both clocks to produce a CLOCK_MONOTONIC-based
clock that ticks even when the device is disabled.
*/
gint64 android_get_time_since_startup(double current_monotonic_time, double current_boottime_time)
{
static double monotonic_start_time = -HUGE_VAL;
static double boottime_start_time = -HUGE_VAL;
static double boottime_adjustment = 0;
static int broken_boottime = 0;
static double broken_boottime_detection_hysteresis = 0.001;
static double adjustment_hysteresis_when_bootime_good = 0.001;
static double adjustment_hysteresis_when_bootime_broken = 8;

if (monotonic_start_time == -HUGE_VAL)
monotonic_start_time = current_monotonic_time;
if (boottime_start_time == -HUGE_VAL)
boottime_start_time = current_boottime_time;
double monotonicSinceStart = current_monotonic_time - monotonic_start_time;
double boottimeSinceStart = current_boottime_time - boottime_start_time;
/* In theory, boottime can only go faster than monotonic, so whenever we detect
this condition we assume that device was asleep and we must adjust the returned
time by the amount of time that the boottime jumped forwards.
In the real world, boottime can go slower than monotonic or even backwards.
We work around this by only taking into account the total difference between
boottime and monotonic times and only adjusting monotonic time when this difference
increases.
There's also a problem that on some devices the boottime continuously jumps
forwards and backwards by ~4 seconds. This means that a naive implementation would
often do more than one time jump after device sleeps, depending on which part
of the jump "cycle" we landed. We work around this by introducing hysteresis of
hysteresisSeconds seconds and adjusting monotonic time only when this adjustment
changes by more than hysteresisSeconds amount, but only on broken devices.
On devices with broken CLOCK_BOOTTIME behaviour this would ignore device sleeps of
hysteresisSeconds or less, which is small compromise to make.
*/
if (boottimeSinceStart - monotonicSinceStart < -broken_boottime_detection_hysteresis)
broken_boottime = 1;
double hysteresisSeconds = broken_boottime ? adjustment_hysteresis_when_bootime_broken : adjustment_hysteresis_when_bootime_good;
if (boottimeSinceStart - monotonicSinceStart > boottime_adjustment + hysteresisSeconds)
boottime_adjustment = boottimeSinceStart - monotonicSinceStart;
return (gint64)(monotonicSinceStart + boottime_adjustment);
}
#endif

#if defined(CLOCK_MONOTONIC)
static gint64
get_posix_time_for_class(int clock_class)
{
struct timespec tspec;
static struct timespec tspec_freq = {0};
static int can_use_clock = 0;
if (!tspec_freq.tv_nsec) {
can_use_clock = clock_getres (clock_class, &tspec_freq) == 0;
/*printf ("resolution: %lu.%lu\n", tspec_freq.tv_sec, tspec_freq.tv_nsec);*/
}
if (can_use_clock) {
if (clock_gettime (clock_class, &tspec) == 0) {
/*printf ("time: %lu.%lu\n", tspec.tv_sec, tspec.tv_nsec); */
return ((gint64)tspec.tv_sec * MTICKS_PER_SEC + tspec.tv_nsec / 100);
}
}
}
#endif

static gint64
get_boot_time (void)
{
Expand Down Expand Up @@ -132,11 +203,7 @@ gint64
mono_msec_boottime (void)
{
#if defined(ANDROID)
struct timespec ts;
if (clock_gettime (CLOCK_MONOTONIC, &ts) != 0) {
return (gint64)MADEUP_BOOT_TIME;
}
return (ts.tv_sec * 1000) + (ts.tv_nsec / 1000000);
return get_posix_time_for_class(CLOCK_BOOTTIME);
#else
static gint64 boot_time = 0;
gint64 now;
Expand All @@ -163,19 +230,11 @@ mono_100ns_ticks (void)
}
return now * timebase.numer / timebase.denom;
#elif defined(CLOCK_MONOTONIC)
struct timespec tspec;
static struct timespec tspec_freq = {0};
static int can_use_clock = 0;
if (!tspec_freq.tv_nsec) {
can_use_clock = clock_getres (CLOCK_MONOTONIC, &tspec_freq) == 0;
/*printf ("resolution: %lu.%lu\n", tspec_freq.tv_sec, tspec_freq.tv_nsec);*/
}
if (can_use_clock) {
if (clock_gettime (CLOCK_MONOTONIC, &tspec) == 0) {
/*printf ("time: %lu.%lu\n", tspec.tv_sec, tspec.tv_nsec); */
return ((gint64)tspec.tv_sec * MTICKS_PER_SEC + tspec.tv_nsec / 100);
}
}
#if defined(ANDROID)
return android_get_time_since_startup(get_posix_time_for_class(CLOCK_BOOTTIME), get_posix_time_for_class(CLOCK_MONOTONIC));
#else
return get_posix_time_for_class(CLOCK_MONOTONIC);
#endif
#endif
if (gettimeofday (&tv, NULL) == 0)
return ((gint64)tv.tv_sec * 1000000 + tv.tv_usec) * 10;
Expand Down