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.
///
/// Needed for unit tests. Must be called after `time_init()`.
/// Needed for unit tests.
void early_init(mparm_T *paramp)
{
estack_init();
@ -261,7 +261,6 @@ int main(int argc, char **argv)
mparm_T params; // various parameters passed between
// main() and other functions.
char *cwd = NULL; // current working dir on startup
time_init();
// 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.

View File

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

View File

@ -24,20 +24,10 @@
struct tm;
static uv_mutex_t delay_mutex;
static uv_cond_t delay_cond;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/time.c.generated.h" // IWYU pragma: export
#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
/// to an arbitrary time in the past.
///
@ -73,55 +63,28 @@ uint64_t os_now(void)
void os_delay(uint64_t ms, bool ignoreinput)
{
DLOG("%" PRIu64 " ms", ms);
if (ignoreinput) {
if (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)
///
/// @param us Number of microseconds to sleep.
/// @param ignoreinput If true, ignore all input (including SIGINT/CTRL-C).
/// If false, waiting is aborted on any input.
void os_microdelay(uint64_t us, bool ignoreinput)
void os_sleep(uint64_t ms)
{
uint64_t elapsed = 0U;
uint64_t base = uv_hrtime();
// 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;
if (ms > UINT_MAX) {
ms = UINT_MAX;
}
const uint64_t now = uv_hrtime();
elapsed += now - base;
base = now;
}
uv_mutex_unlock(&delay_mutex);
uv_sleep((unsigned)ms);
}
// 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));
ui_call_flush();
uint64_t wd = (uint64_t)labs(p_wd);
os_microdelay(wd * 1000U, true);
os_sleep(wd);
pending_cursor_update = true; // restore the cursor later
}
}
@ -521,7 +521,7 @@ void ui_flush(void)
ui_call_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();
uint64_t wd = (uint64_t)labs(p_wd);
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)

View File

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