From 74d154c706a1bf1266f7b1b2a2ff63fff12d82f9 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 21 Mar 2019 14:13:42 -0700 Subject: [PATCH] Use CLOCK_MONOTONIC on platforms that support it This way, timeouts will apply even if the current process is starved for CPU. --- lib/src/clock.h | 125 +++++++++++++++++++++++++++++++++++++++++++---- lib/src/parser.c | 20 +++++--- 2 files changed, 128 insertions(+), 17 deletions(-) diff --git a/lib/src/clock.h b/lib/src/clock.h index 3193a6b3..2792febb 100644 --- a/lib/src/clock.h +++ b/lib/src/clock.h @@ -3,30 +3,137 @@ #include +typedef uint64_t TSDuration; + #ifdef _WIN32 -#include +// Windows: +// * Represent a time as a performance counter value. +// * Represent a duration as a number of performance counter ticks. -static inline uint64_t get_clock() { +#include +typedef uint64_t TSClock; + +static inline TSDuration duration_from_micros(uint64_t micros) { + LARGE_INTEGER frequency; + QueryPerformanceFrequency(&frequency); + return micros * (uint64_t)frequency.QuadPart / 1000000; +} + +static inline uint64_t duration_to_micros(TSDuration self) { + LARGE_INTEGER frequency; + QueryPerformanceFrequency(&frequency); + return self * 1000000 / (uint64_t)frequency.QuadPart; +} + +static inline TSClock clock_null() { + return 0; +} + +static inline TSClock clock_now() { LARGE_INTEGER result; QueryPerformanceCounter(&result); return (uint64_t)result.QuadPart; } -static inline uint64_t get_clocks_per_second() { - LARGE_INTEGER result; - QueryPerformanceFrequency(&result); - return (uint64_t)result.QuadPart; +static inline TSClock clock_after(TSClock base, TSDuration duration) { + return base + duration; +} + +static inline bool clock_is_null(TSClock self) { + return !self; +} + +static inline bool clock_is_gt(TSClock self, TSClock other) { + return self > other; +} + +#elif defined(CLOCK_MONOTONIC) + +// POSIX with monotonic clock support (Linux, macOS >= 10.12) +// * Represent a time as a monotonic (seconds, nanoseconds) pair. +// * Represent a duration as a number of microseconds. +// +// On these platforms, parse timeouts will correspond accurately to +// real time, regardless of what other processes are running. + +#include +typedef struct timespec TSClock; + +static inline TSDuration duration_from_micros(uint64_t micros) { + return micros; +} + +static inline uint64_t duration_to_micros(TSDuration self) { + return self; +} + +static inline TSClock clock_now() { + TSClock result; + clock_gettime(CLOCK_MONOTONIC, &result); + return result; +} + +static inline TSClock clock_null() { + return (TSClock) {0, 0}; +} + +static inline TSClock clock_after(TSClock base, TSDuration duration) { + TSClock result = base; + result.tv_sec += duration / 1000000; + result.tv_nsec += (duration % 1000000) * 1000; + return result; +} + +static inline bool clock_is_null(TSClock self) { + return !self.tv_sec; +} + +static inline bool clock_is_gt(TSClock self, TSClock other) { + if (self.tv_sec > other.tv_sec) return true; + if (self.tv_sec < other.tv_sec) return false; + return self.tv_nsec > other.tv_nsec; } #else -static inline uint64_t get_clock() { +// POSIX without monotonic clock support +// * Represent a time as a process clock value. +// * Represent a duration as a number of process clock ticks. +// +// On these platforms, parse timeouts may be affected by other processes, +// which is not ideal, but is better than using a non-monotonic time API +// like `gettimeofday`. + +#include +typedef uint64_t TSClock; + +static inline TSDuration duration_from_micros(uint64_t micros) { + return micros * (uint64_t)CLOCKS_PER_SEC / 1000000; +} + +static inline uint64_t duration_to_micros(TSDuration self) { + return self * 1000000 / (uint64_t)CLOCKS_PER_SEC; +} + +static inline TSClock clock_null() { + return 0; +} + +static inline TSClock clock_now() { return (uint64_t)clock(); } -static inline uint64_t get_clocks_per_second() { - return (uint64_t)CLOCKS_PER_SEC; +static inline TSClock clock_after(TSClock base, TSDuration duration) { + return base + duration; +} + +static inline bool clock_is_null(TSClock self) { + return !self; +} + +static inline bool clock_is_gt(TSClock self, TSClock other) { + return self > other; } #endif diff --git a/lib/src/parser.c b/lib/src/parser.c index 39699866..3937bd64 100644 --- a/lib/src/parser.c +++ b/lib/src/parser.c @@ -66,9 +66,9 @@ struct TSParser { ReusableNode reusable_node; void *external_scanner_payload; FILE *dot_graph_file; + TSClock end_clock; + TSDuration timeout_duration; unsigned accept_count; - uint64_t clock_limit; - uint64_t start_clock; unsigned operation_count; const volatile uint32_t *cancellation_flag; bool halt_on_error; @@ -1288,7 +1288,7 @@ static bool ts_parser__advance( self->operation_count = 0; if ( (self->cancellation_flag && atomic_load(self->cancellation_flag)) || - (self->clock_limit && get_clock() - self->start_clock > self->clock_limit) + (!clock_is_null(self->end_clock) && clock_is_gt(clock_now(), self->end_clock)) ) { ts_subtree_release(&self->tree_pool, lookahead); return false; @@ -1513,8 +1513,8 @@ TSParser *ts_parser_new() { self->dot_graph_file = NULL; self->halt_on_error = false; self->cancellation_flag = NULL; - self->clock_limit = 0; - self->start_clock = 0; + self->timeout_duration = 0; + self->end_clock = clock_null(); self->operation_count = 0; self->old_tree = NULL_SUBTREE; self->scratch_tree.ptr = &self->scratch_tree_data; @@ -1598,11 +1598,11 @@ void ts_parser_set_cancellation_flag(TSParser *self, const uint32_t *flag) { } uint64_t ts_parser_timeout_micros(const TSParser *self) { - return self->clock_limit * 1000000 / get_clocks_per_second(); + return duration_to_micros(self->timeout_duration); } void ts_parser_set_timeout_micros(TSParser *self, uint64_t timeout_micros) { - self->clock_limit = timeout_micros * get_clocks_per_second() / 1000000; + self->timeout_duration = duration_from_micros(timeout_micros); } void ts_parser_set_included_ranges(TSParser *self, const TSRange *ranges, uint32_t count) { @@ -1666,7 +1666,11 @@ TSTree *ts_parser_parse(TSParser *self, const TSTree *old_tree, TSInput input) { uint32_t position = 0, last_position = 0, version_count = 0; self->operation_count = 0; - self->start_clock = get_clock(); + if (self->timeout_duration) { + self->end_clock = clock_after(clock_now(), self->timeout_duration); + } else { + self->end_clock = clock_null(); + } do { for (StackVersion version = 0;