# HG changeset patch # User Christian Brabandt # Date 1477592104 -7200 # Node ID 88331ee68367189141e257041d2151517757c53f # Parent c6e8a776a1ed65fd5675b52a7d1e9057b88058e7 commit https://github.com/vim/vim/commit/01688ad545ff0809ddad5c8fa6b149dc5d67312b Author: Bram Moolenaar Date: Thu Oct 27 20:00:07 2016 +0200 patch 8.0.0050 Problem: An exiting job is detected with a large latency. Solution: Check for pending job more often. (Ozaki Kiichi) Change the double loop in mch_inchar() into one. diff --git a/src/channel.c b/src/channel.c --- a/src/channel.c +++ b/src/channel.c @@ -4643,8 +4643,8 @@ job_stop_on_exit(void) } /* - * Return TRUE when there is any job that might exit, which means - * job_check_ended() should be called once in a while. + * Return TRUE when there is any job that has an exit callback and might exit, + * which means job_check_ended() should be called more often. */ int has_pending_job(void) @@ -4652,7 +4652,11 @@ has_pending_job(void) job_T *job; for (job = first_job; job != NULL; job = job->jv_next) - if (job_still_alive(job)) + /* Only should check if the channel has been closed, if the channel is + * open the job won't exit. */ + if (job->jv_status == JOB_STARTED && job->jv_exit_cb != NULL + && (job->jv_channel == NULL + || !channel_still_useful(job->jv_channel))) return TRUE; return FALSE; } diff --git a/src/os_unix.c b/src/os_unix.c --- a/src/os_unix.c +++ b/src/os_unix.c @@ -404,139 +404,121 @@ mch_inchar( { int len; int interrupted = FALSE; + int did_start_blocking = FALSE; long wait_time; + long elapsed_time = 0; #if defined(HAVE_GETTIMEOFDAY) && defined(HAVE_SYS_TIME_H) struct timeval start_tv; gettimeofday(&start_tv, NULL); #endif -#ifdef MESSAGE_QUEUE - parse_queued_messages(); -#endif - - /* Check if window changed size while we were busy, perhaps the ":set - * columns=99" command was used. */ - while (do_resize) - handle_resize(); - + /* repeat until we got a character or waited long enough */ for (;;) { - if (wtime >= 0) - wait_time = wtime; - else - wait_time = p_ut; -#if defined(HAVE_GETTIMEOFDAY) && defined(HAVE_SYS_TIME_H) - wait_time -= elapsed(&start_tv); - if (wait_time >= 0) - { -#endif - if (WaitForChar(wait_time, &interrupted)) - break; - - /* no character available */ - if (do_resize) - { - handle_resize(); - continue; - } -#ifdef FEAT_CLIENTSERVER - if (server_waiting()) - { - parse_queued_messages(); - continue; - } -#endif -#ifdef MESSAGE_QUEUE - if (interrupted) - { - parse_queued_messages(); - continue; - } -#endif -#if defined(HAVE_GETTIMEOFDAY) && defined(HAVE_SYS_TIME_H) - } -#endif - if (wtime >= 0) - /* no character available within "wtime" */ - return 0; - - /* wtime == -1: no character available within 'updatetime' */ -#ifdef FEAT_AUTOCMD - if (trigger_cursorhold() && maxlen >= 3 - && !typebuf_changed(tb_change_cnt)) - { - buf[0] = K_SPECIAL; - buf[1] = KS_EXTRA; - buf[2] = (int)KE_CURSORHOLD; - return 3; - } -#endif - /* - * If there is no character available within 'updatetime' seconds - * flush all the swap files to disk. - * Also done when interrupted by SIGWINCH. - */ - before_blocking(); - break; - } - - /* repeat until we got a character */ - for (;;) - { - long wtime_now = -1L; - - while (do_resize) /* window changed size */ + /* Check if window changed size while we were busy, perhaps the ":set + * columns=99" command was used. */ + while (do_resize) handle_resize(); #ifdef MESSAGE_QUEUE parse_queued_messages(); - -# ifdef FEAT_JOB_CHANNEL - if (has_pending_job()) +#endif + if (wtime < 0 && did_start_blocking) + /* blocking and already waited for p_ut */ + wait_time = -1; + else { - /* Don't wait longer than a few seconds, checking for a finished - * job requires polling. */ - if (p_ut > 9000L) - wtime_now = 1000L; + if (wtime >= 0) + wait_time = wtime; else - wtime_now = 10000L - p_ut; + /* going to block after p_ut */ + wait_time = p_ut; +#if defined(HAVE_GETTIMEOFDAY) && defined(HAVE_SYS_TIME_H) + elapsed_time = elapsed(&start_tv); +#endif + wait_time -= elapsed_time; + if (wait_time < 0) + { + if (wtime >= 0) + /* no character available within "wtime" */ + return 0; + + if (wtime < 0) + { + /* no character available within 'updatetime' */ + did_start_blocking = TRUE; +#ifdef FEAT_AUTOCMD + if (trigger_cursorhold() && maxlen >= 3 + && !typebuf_changed(tb_change_cnt)) + { + buf[0] = K_SPECIAL; + buf[1] = KS_EXTRA; + buf[2] = (int)KE_CURSORHOLD; + return 3; + } +#endif + /* + * If there is no character available within 'updatetime' + * seconds flush all the swap files to disk. + * Also done when interrupted by SIGWINCH. + */ + before_blocking(); + continue; + } + } } -# endif -#endif + +#ifdef FEAT_JOB_CHANNEL + /* Checking if a job ended requires polling. Do this every 100 msec. */ + if (has_pending_job() && (wait_time < 0 || wait_time > 100L)) + wait_time = 100L; +#endif + /* * We want to be interrupted by the winch signal * or by an event on the monitored file descriptors. */ - if (!WaitForChar(wtime_now, &interrupted)) + if (WaitForChar(wait_time, &interrupted)) { - if (do_resize) /* interrupted by SIGWINCH signal */ - continue; -#ifdef MESSAGE_QUEUE - if (interrupted || wtime_now > 0) - { - parse_queued_messages(); - continue; - } -#endif - return 0; + /* If input was put directly in typeahead buffer bail out here. */ + if (typebuf_changed(tb_change_cnt)) + return 0; + + /* + * For some terminals we only get one character at a time. + * We want the get all available characters, so we could keep on + * trying until none is available + * For some other terminals this is quite slow, that's why we don't + * do it. + */ + len = read_from_input_buf(buf, (long)maxlen); + if (len > 0) + return len; + continue; } - /* If input was put directly in typeahead buffer bail out here. */ - if (typebuf_changed(tb_change_cnt)) - return 0; - - /* - * For some terminals we only get one character at a time. - * We want the get all available characters, so we could keep on - * trying until none is available - * For some other terminals this is quite slow, that's why we don't do - * it. - */ - len = read_from_input_buf(buf, (long)maxlen); - if (len > 0) - return len; - } + /* no character available */ +#if !(defined(HAVE_GETTIMEOFDAY) && defined(HAVE_SYS_TIME_H)) + /* estimate the elapsed time */ + elapsed += wait_time; +#endif + + if (do_resize /* interrupted by SIGWINCH signal */ +#ifdef FEAT_CLIENTSERVER + || server_waiting() +#endif +#ifdef MESSAGE_QUEUE + || interrupted +#endif + || wait_time > 0 + || !did_start_blocking) + continue; + + /* no character available or interrupted */ + break; + } + return 0; } static void diff --git a/src/testdir/shared.vim b/src/testdir/shared.vim --- a/src/testdir/shared.vim +++ b/src/testdir/shared.vim @@ -136,6 +136,34 @@ func WaitFor(expr) return 1000 endfunc +" Wait for up to a given milliseconds. +" With the +timers feature this waits for key-input by getchar(), Resume() +" feeds key-input and resumes process. Return time waited in milliseconds. +" Without +timers it uses simply :sleep. +func Standby(msec) + if has('timers') + let start = reltime() + let g:_standby_timer = timer_start(a:msec, function('s:feedkeys')) + call getchar() + return float2nr(reltimefloat(reltime(start)) * 1000) + else + execute 'sleep ' a:msec . 'm' + return a:msec + endif +endfunc + +func Resume() + if exists('g:_standby_timer') + call timer_stop(g:_standby_timer) + call s:feedkeys(0) + unlet g:_standby_timer + endif +endfunc + +func s:feedkeys(timer) + call feedkeys('x', 'nt') +endfunc + " Run Vim, using the "vimcmd" file and "-u NORC". " "before" is a list of Vim commands to be executed before loading plugins. " "after" is a list of Vim commands to be executed after loading plugins. diff --git a/src/testdir/test_channel.vim b/src/testdir/test_channel.vim --- a/src/testdir/test_channel.vim +++ b/src/testdir/test_channel.vim @@ -1362,9 +1362,11 @@ func Test_exit_callback() endif endfunc -let g:exit_cb_time = {'start': 0, 'end': 0} function MyExitTimeCb(job, status) - let g:exit_cb_time.end = reltime(g:exit_cb_time.start) + if job_info(a:job).process == g:exit_cb_val.process + let g:exit_cb_val.end = reltime(g:exit_cb_val.start) + endif + call Resume() endfunction func Test_exit_callback_interval() @@ -1372,11 +1374,30 @@ func Test_exit_callback_interval() return endif - let g:exit_cb_time.start = reltime() + let g:exit_cb_val = {'start': reltime(), 'end': 0, 'process': 0} let job = job_start([s:python, '-c', 'import time;time.sleep(0.5)'], {'exit_cb': 'MyExitTimeCb'}) - call WaitFor('g:exit_cb_time.end != 0') - let elapsed = reltimefloat(g:exit_cb_time.end) - call assert_true(elapsed > 0.3) + let g:exit_cb_val.process = job_info(job).process + call WaitFor('type(g:exit_cb_val.end) != v:t_number || g:exit_cb_val.end != 0') + let elapsed = reltimefloat(g:exit_cb_val.end) + call assert_true(elapsed > 0.5) + call assert_true(elapsed < 1.0) + + " case: unreferenced job, using timer + if !has('timers') + return + endif + + let g:exit_cb_val = {'start': reltime(), 'end': 0, 'process': 0} + let g:job = job_start([s:python, '-c', 'import time;time.sleep(0.5)'], {'exit_cb': 'MyExitTimeCb'}) + let g:exit_cb_val.process = job_info(g:job).process + unlet g:job + call Standby(1000) + if type(g:exit_cb_val.end) != v:t_number || g:exit_cb_val.end != 0 + let elapsed = reltimefloat(g:exit_cb_val.end) + else + let elapsed = 1.0 + endif + call assert_true(elapsed > 0.5) call assert_true(elapsed < 1.0) endfunc diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -765,6 +765,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 50, +/**/ 49, /**/ 48,