mirror of
https://github.com/neovim/neovim.git
synced 2026-11-06 05:16:52 +00:00
fix(event): wait(), jobwait() timeouts #42599
Problem: - wait() never times out if each check returns in under 1 ms (e.g. the condition closes a timer), because each pass truncates the elapsed time to whole milliseconds. - jobwait() with a timeout returns -1 for all jobs after the first one that exits. - CursorHold drops sub-millisecond waits, so frequent events postpone it. - wait() leaks the condition result if it is a String, List, etc. Solution: - Compute the remaining time from a fixed start time. - Track the CursorHold wait time in nanoseconds. - Free the condition result.
This commit is contained in:
1 parent
bf4b0ea764
commit
7fa72edcd4
5 files changed
+29
-19
No files matched your search
@@ -2408,7 +2408,8 @@ static void f_wait(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
||||
ui_flush();
|
||||
|
||||
LOOP_PROCESS_EVENTS_UNTIL(&main_loop, main_loop.events, timeout,
|
||||
eval_expr_typval(&expr, false, &argv, 0, &exprval) != OK
|
||||
(tv_clear(&exprval),
|
||||
eval_expr_typval(&expr, false, &argv, 0, &exprval) != OK)
|
||||
|| tv_get_number_chk(&exprval, &error)
|
||||
|| called_emsg > called_emsg_before || error || got_int);
|
||||
|
||||
@@ -2421,6 +2422,7 @@ static void f_wait(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
||||
} else if (tv_get_number_chk(&exprval, &error)) {
|
||||
rettv->vval.v_number = 0;
|
||||
}
|
||||
tv_clear(&exprval);
|
||||
|
||||
// Stop dummy timer
|
||||
time_watcher_stop(tw);
|
||||
@@ -3579,12 +3581,13 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
||||
i++;
|
||||
});
|
||||
|
||||
int remaining = -1;
|
||||
uint64_t before = 0;
|
||||
int timeout = -1;
|
||||
uint64_t start = 0;
|
||||
if (argvars[1].v_type == VAR_NUMBER && argvars[1].vval.v_number >= 0) {
|
||||
remaining = (int)argvars[1].vval.v_number;
|
||||
before = os_hrtime();
|
||||
timeout = (int)argvars[1].vval.v_number;
|
||||
start = os_hrtime();
|
||||
}
|
||||
int remaining = timeout;
|
||||
|
||||
// Only mark the UI as busy when jobwait() blocks
|
||||
const bool busy = remaining != 0;
|
||||
@@ -3606,9 +3609,7 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
||||
break; // Interrupted (CTRL-C) or timeout, skip remaining jobs.
|
||||
}
|
||||
if (remaining > 0) {
|
||||
uint64_t now = os_hrtime();
|
||||
remaining = MIN(0, remaining - (int)((now - before) / 1000000));
|
||||
before = now;
|
||||
remaining = MAX(0, timeout - (int)((os_hrtime() - start) / 1000000));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,16 +25,15 @@
|
||||
// Poll for events until a condition or timeout
|
||||
#define LOOP_PROCESS_EVENTS_UNTIL(loop, multiqueue, timeout, condition) \
|
||||
do { \
|
||||
int64_t remaining = timeout; \
|
||||
uint64_t before = (remaining > 0) ? os_hrtime() : 0; \
|
||||
const int64_t timeout_ms = (timeout); \
|
||||
const uint64_t start = (timeout_ms > 0) ? os_hrtime() : 0; \
|
||||
int64_t remaining = timeout_ms; \
|
||||
while (!(condition)) { \
|
||||
LOOP_PROCESS_EVENTS(loop, multiqueue, remaining); \
|
||||
if (remaining == 0) { \
|
||||
break; \
|
||||
} else if (remaining > 0) { \
|
||||
uint64_t now = os_hrtime(); \
|
||||
remaining -= (int64_t)((now - before) / 1000000); \
|
||||
before = now; \
|
||||
remaining = timeout_ms - (int64_t)((os_hrtime() - start) / 1000000); \
|
||||
if (remaining <= 0) { \
|
||||
break; \
|
||||
} \
|
||||
|
||||
+4
-4
@@ -43,7 +43,7 @@ static char *input_read_pos = input_buffer;
|
||||
static char *input_write_pos = input_buffer;
|
||||
|
||||
static bool blocking = false;
|
||||
static int cursorhold_time = 0; ///< time waiting for CursorHold event
|
||||
static uint64_t cursorhold_time = 0; ///< Time (ns) spent waiting for CursorHold event.
|
||||
static int cursorhold_tb_change_cnt = 0; ///< tb_change_cnt when waiting started
|
||||
|
||||
#include "os/input.c.generated.h"
|
||||
@@ -120,7 +120,7 @@ int input_get(uint8_t *buf, int maxlen, int ms, int tb_change_cnt, MultiQueue *e
|
||||
}
|
||||
} else {
|
||||
uint64_t wait_start = os_hrtime();
|
||||
cursorhold_time = MIN(cursorhold_time, (int)p_ut);
|
||||
cursorhold_time = MIN(cursorhold_time, (uint64_t)p_ut * 1000000);
|
||||
// When an autocomplete is pending, wake at the sooner of
|
||||
// 'autocompletedelay' and 'updatetime' so the delay does not postpone
|
||||
// CursorHold. Once CursorHold has fired, only the delay is left.
|
||||
@@ -133,7 +133,7 @@ int input_get(uint8_t *buf, int maxlen, int ms, int tb_change_cnt, MultiQueue *e
|
||||
&& (get_real_state() & MODE_INSERT) != 0;
|
||||
// Measure the delay from when it was armed (the keystroke), so a
|
||||
// CursorHold returning in between does not push the popup back.
|
||||
int64_t wait_time = p_ut - cursorhold_time;
|
||||
int64_t wait_time = p_ut - (int64_t)(cursorhold_time / 1000000);
|
||||
if (delay_pending) {
|
||||
int64_t delay_left = p_acl - ins_compl_autocomplete_elapsed();
|
||||
wait_time = MIN(MAX(delay_left, 0), wait_time);
|
||||
@@ -168,7 +168,7 @@ int input_get(uint8_t *buf, int maxlen, int ms, int tb_change_cnt, MultiQueue *e
|
||||
inbuf_poll(-1, events);
|
||||
}
|
||||
} else {
|
||||
cursorhold_time += (int)((os_hrtime() - wait_start) / 1000000);
|
||||
cursorhold_time += os_hrtime() - wait_start;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1022,15 +1022,20 @@ describe('jobs', function()
|
||||
|
||||
it('can pass 0 to check if a job exists', function()
|
||||
source([[
|
||||
call rpcnotify(g:channel, 'wait', jobwait(has('win32') ? [
|
||||
let g:jobs = has('win32') ? [
|
||||
\ jobstart('Start-Sleep -Milliseconds 50; exit 4'),
|
||||
\ jobstart('Start-Sleep -Milliseconds 300; exit 5'),
|
||||
\ ] : [
|
||||
\ jobstart('sleep 0.05; exit 4'),
|
||||
\ jobstart('sleep 0.3; exit 5'),
|
||||
\ ], 0))
|
||||
\ ]
|
||||
call rpcnotify(g:channel, 'wait', jobwait(g:jobs, 0))
|
||||
]])
|
||||
eq({ 'notification', 'wait', { { -1, -1 } } }, next_msg())
|
||||
|
||||
-- With a timeout, keeps waiting for the second job after the first one exits.
|
||||
command([[call rpcnotify(g:channel, 'wait', jobwait(g:jobs, 10000))]])
|
||||
eq({ 'notification', 'wait', { { 4, 5 } } }, next_msg())
|
||||
end)
|
||||
end)
|
||||
|
||||
|
||||
@@ -36,6 +36,11 @@ describe('wait()', function()
|
||||
it('returns -1 on timeout', function()
|
||||
eq(-1, eval('wait(0, 0)'))
|
||||
eq(-1, eval('wait(50, 0)'))
|
||||
|
||||
-- Each check closes a timer, so every poll returns in under 1 ms. #42538
|
||||
command('let g:Cond = { -> timer_stop(timer_start(10000, { -> 0 })) }')
|
||||
feed(':call rpcnotify(g:channel, "wait", wait(100, g:Cond))<CR>')
|
||||
eq({ 'notification', 'wait', { -1 } }, next_msg(5000))
|
||||
end)
|
||||
|
||||
it('returns -2 when interrupted', function()
|
||||
|
||||
Reference in new issue
Block a user