refactor(time): refactor delay with input checking

Previously, there were three low-level delay entry points

- os_delay(ms, ignoreinput=true): sleep for ms, only break on got_int

- os_delay(ms, ignoreinput=false): sleep for ms, break on any key input
  os_microdelay(us, false): equivalent, but in μs (not directly called)

- os_microdelay(us, true): sleep for μs, never break.

The implementation of the latter two both used uv_cond_timedwait()
This could have been for two reasons:
 1. allow another thread to "interrupt" the wait
 2. uv_cond_timedwait() has higher resolution than uv_sleep()

However we (1) never used the first, even when TUI was a thread, and
(2) nowhere in the codebase are we using μs resolution, it is always a ms
multiplied with 1000.

In addition, os_delay(ms, false) would completely block the thread for
100ms intervals and in between check for input. This is not how event handling
is done alound here.

Therefore:

Replace the implementation of os_delay(ms, false) to use
LOOP_PROCESS_EVENTS_UNTIL which does a proper epoll wait with a timeout,
instead of the 100ms timer panic.

Replace os_microdelay(us, false) with a direct wrapper of uv_sleep.
This commit is contained in:
bfredl 2023-04-23 19:02:23 +02:00
parent 3ac952d4e2
commit 0d2fe77865
6 changed files with 22 additions and 61 deletions

View File

@ -173,7 +173,7 @@ bool event_teardown(void)
/// Performs early initialization. /// Performs early initialization.
/// ///
/// Needed for unit tests. Must be called after `time_init()`. /// Needed for unit tests.
void early_init(mparm_T *paramp) void early_init(mparm_T *paramp)
{ {
estack_init(); estack_init();
@ -261,7 +261,6 @@ int main(int argc, char **argv)
mparm_T params; // various parameters passed between mparm_T params; // various parameters passed between
// main() and other functions. // main() and other functions.
char *cwd = NULL; // current working dir on startup char *cwd = NULL; // current working dir on startup
time_init();
// Many variables are in `params` so that we can pass them around easily. // Many variables are in `params` so that we can pass them around easily.
// `argc` and `argv` are also copied, so that they can be changed. // `argc` and `argv` are also copied, so that they can be changed.

View File

@ -441,7 +441,7 @@ bool input_blocking(void)
// This is a replacement for the old `WaitForChar` function in os_unix.c // This is a replacement for the old `WaitForChar` function in os_unix.c
static InbufPollResult inbuf_poll(int ms, MultiQueue *events) static InbufPollResult inbuf_poll(int ms, MultiQueue *events)
{ {
if (input_ready(events)) { if (os_input_ready(events)) {
return kInputAvail; return kInputAvail;
} }
@ -457,14 +457,14 @@ static InbufPollResult inbuf_poll(int ms, MultiQueue *events)
DLOG("blocking... events_enabled=%d events_pending=%d", events != NULL, DLOG("blocking... events_enabled=%d events_pending=%d", events != NULL,
events && !multiqueue_empty(events)); events && !multiqueue_empty(events));
LOOP_PROCESS_EVENTS_UNTIL(&main_loop, NULL, ms, LOOP_PROCESS_EVENTS_UNTIL(&main_loop, NULL, ms,
input_ready(events) || input_eof); os_input_ready(events) || input_eof);
blocking = false; blocking = false;
if (do_profiling == PROF_YES && ms) { if (do_profiling == PROF_YES && ms) {
prof_inchar_exit(); prof_inchar_exit();
} }
if (input_ready(events)) { if (os_input_ready(events)) {
return kInputAvail; return kInputAvail;
} }
return input_eof ? kInputEof : kInputNone; return input_eof ? kInputEof : kInputNone;
@ -530,8 +530,8 @@ static int push_event_key(uint8_t *buf, int maxlen)
return buf_idx; return buf_idx;
} }
// Check if there's pending input /// Check if there's pending input already in typebuf or `events`
static bool input_ready(MultiQueue *events) bool os_input_ready(MultiQueue *events)
{ {
return (typebuf_was_filled // API call filled typeahead return (typebuf_was_filled // API call filled typeahead
|| rbuffer_size(input_buffer) // Input buffer filled || rbuffer_size(input_buffer) // Input buffer filled

View File

@ -24,20 +24,10 @@
struct tm; struct tm;
static uv_mutex_t delay_mutex;
static uv_cond_t delay_cond;
#ifdef INCLUDE_GENERATED_DECLARATIONS #ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/time.c.generated.h" // IWYU pragma: export # include "os/time.c.generated.h" // IWYU pragma: export
#endif #endif
/// Initializes the time module
void time_init(void)
{
uv_mutex_init(&delay_mutex);
uv_cond_init(&delay_cond);
}
/// Gets a high-resolution (nanosecond), monotonically-increasing time relative /// Gets a high-resolution (nanosecond), monotonically-increasing time relative
/// to an arbitrary time in the past. /// to an arbitrary time in the past.
/// ///
@ -73,55 +63,28 @@ uint64_t os_now(void)
void os_delay(uint64_t ms, bool ignoreinput) void os_delay(uint64_t ms, bool ignoreinput)
{ {
DLOG("%" PRIu64 " ms", ms); DLOG("%" PRIu64 " ms", ms);
if (ignoreinput) { if (ms > INT_MAX) {
if (ms > INT_MAX) { ms = INT_MAX;
ms = INT_MAX;
}
LOOP_PROCESS_EVENTS_UNTIL(&main_loop, NULL, (int)ms, got_int);
} else {
os_microdelay(ms * 1000U, ignoreinput);
} }
LOOP_PROCESS_EVENTS_UNTIL(&main_loop, NULL, (int)ms,
ignoreinput ? got_int : os_input_ready(NULL));
} }
/// Sleeps for `us` microseconds. /// Sleeps for `ms` milliseconds without checking for events or interrupts.
///
/// This blocks even "fast" events which is quite disruptive. This should only
/// be used in debug code. Prefer os_delay() and decide if the delay should be
/// interupted by input or only a CTRL-C.
/// ///
/// @see uv_sleep() (libuv v1.34.0) /// @see uv_sleep() (libuv v1.34.0)
/// ///
/// @param us Number of microseconds to sleep. /// @param us Number of microseconds to sleep.
/// @param ignoreinput If true, ignore all input (including SIGINT/CTRL-C). void os_sleep(uint64_t ms)
/// If false, waiting is aborted on any input.
void os_microdelay(uint64_t us, bool ignoreinput)
{ {
uint64_t elapsed = 0U; if (ms > UINT_MAX) {
uint64_t base = uv_hrtime(); ms = UINT_MAX;
// Convert microseconds to nanoseconds, or UINT64_MAX on overflow.
const uint64_t ns = (us < UINT64_MAX / 1000U) ? us * 1000U : UINT64_MAX;
uv_mutex_lock(&delay_mutex);
while (elapsed < ns) {
// If ignoring input, we simply wait the full delay.
// Else we check for input in ~100ms intervals.
const uint64_t ns_delta = ignoreinput
? ns - elapsed
: MIN(ns - elapsed, 100000000U); // 100ms
const int rv = uv_cond_timedwait(&delay_cond, &delay_mutex, ns_delta);
if (0 != rv && UV_ETIMEDOUT != rv) {
abort();
break;
} // Else: Timeout proceeded normally.
if (!ignoreinput && os_char_avail()) {
break;
}
const uint64_t now = uv_hrtime();
elapsed += now - base;
base = now;
} }
uv_sleep((unsigned)ms);
uv_mutex_unlock(&delay_mutex);
} }
// Cache of the current timezone name as retrieved from TZ, or an empty string // Cache of the current timezone name as retrieved from TZ, or an empty string

View File

@ -439,7 +439,7 @@ void ui_line(ScreenGrid *grid, int row, int startcol, int endcol, int clearcol,
MIN(clearcol, (int)grid->cols - 1)); MIN(clearcol, (int)grid->cols - 1));
ui_call_flush(); ui_call_flush();
uint64_t wd = (uint64_t)labs(p_wd); uint64_t wd = (uint64_t)labs(p_wd);
os_microdelay(wd * 1000U, true); os_sleep(wd);
pending_cursor_update = true; // restore the cursor later pending_cursor_update = true; // restore the cursor later
} }
} }
@ -521,7 +521,7 @@ void ui_flush(void)
ui_call_flush(); ui_call_flush();
if (p_wd && (rdb_flags & RDB_FLUSH)) { if (p_wd && (rdb_flags & RDB_FLUSH)) {
os_microdelay((uint64_t)labs(p_wd) * 1000U, true); os_sleep((uint64_t)labs(p_wd));
} }
} }

View File

@ -467,7 +467,7 @@ static void debug_delay(Integer lines)
ui_call_flush(); ui_call_flush();
uint64_t wd = (uint64_t)labs(p_wd); uint64_t wd = (uint64_t)labs(p_wd);
uint64_t factor = (uint64_t)MAX(MIN(lines, 5), 1); uint64_t factor = (uint64_t)MAX(MIN(lines, 5), 1);
os_microdelay(factor * wd * 1000U, true); os_sleep(factor * wd);
} }
static void compose_area(Integer startrow, Integer endrow, Integer startcol, Integer endcol) static void compose_area(Integer startrow, Integer endrow, Integer startcol, Integer endcol)

View File

@ -97,7 +97,6 @@ local init = only_separate(function()
for _, c in ipairs(child_calls_init) do for _, c in ipairs(child_calls_init) do
c.func(unpack(c.args)) c.func(unpack(c.args))
end end
libnvim.time_init()
libnvim.fs_init() libnvim.fs_init()
libnvim.event_init() libnvim.event_init()
libnvim.early_init(nil) libnvim.early_init(nil)