# HG changeset patch # User Christian Brabandt # Date 1476549904 -7200 # Node ID c5c15c818bda8a078c01acb648179d7a44ecb8eb # Parent abb9c83e2b7c77ff15a1aa6f6b71d26591c200d0 commit https://github.com/vim/vim/commit/97792de2762cc79cc365a8a0b858f27753179577 Author: Bram Moolenaar Date: Sat Oct 15 18:36:49 2016 +0200 patch 8.0.0036 Problem: Detecting that a job has finished may take a while. Solution: Check for a finished job more often (Ozaki Kiichi) diff --git a/src/channel.c b/src/channel.c --- a/src/channel.c +++ b/src/channel.c @@ -4428,6 +4428,39 @@ job_free(job_T *job) } } + static void +job_cleanup(job_T *job) +{ + if (job->jv_status != JOB_ENDED) + return; + + if (job->jv_exit_cb != NULL) + { + typval_T argv[3]; + typval_T rettv; + int dummy; + + /* invoke the exit callback; make sure the refcount is > 0 */ + ++job->jv_refcount; + argv[0].v_type = VAR_JOB; + argv[0].vval.v_job = job; + argv[1].v_type = VAR_NUMBER; + argv[1].vval.v_number = job->jv_exitval; + call_func(job->jv_exit_cb, (int)STRLEN(job->jv_exit_cb), + &rettv, 2, argv, NULL, 0L, 0L, &dummy, TRUE, + job->jv_exit_partial, NULL); + clear_tv(&rettv); + --job->jv_refcount; + channel_need_redraw = TRUE; + } + if (job->jv_refcount == 0) + { + /* The job was already unreferenced, now that it ended it can be + * freed. Careful: caller must not use "job" after this! */ + job_free(job); + } +} + #if defined(EXITFREE) || defined(PROTO) void job_free_all(void) @@ -4445,10 +4478,15 @@ job_free_all(void) static int job_still_useful(job_T *job) { - return job->jv_status == JOB_STARTED - && (job->jv_stoponexit != NULL || job->jv_exit_cb != NULL - || (job->jv_channel != NULL - && channel_still_useful(job->jv_channel))); + return (job->jv_stoponexit != NULL || job->jv_exit_cb != NULL + || (job->jv_channel != NULL + && channel_still_useful(job->jv_channel))); +} + + static int +job_still_alive(job_T *job) +{ + return (job->jv_status == JOB_STARTED) && job_still_useful(job); } /* @@ -4462,7 +4500,7 @@ set_ref_in_job(int copyID) typval_T tv; for (job = first_job; job != NULL; job = job->jv_next) - if (job_still_useful(job)) + if (job_still_alive(job)) { tv.v_type = VAR_JOB; tv.vval.v_job = job; @@ -4478,7 +4516,7 @@ job_unref(job_T *job) { /* Do not free the job when it has not ended yet and there is a * "stoponexit" flag or an exit callback. */ - if (!job_still_useful(job)) + if (!job_still_alive(job)) { job_free(job); } @@ -4503,7 +4541,7 @@ free_unused_jobs_contents(int copyID, in for (job = first_job; job != NULL; job = job->jv_next) if ((job->jv_copyID & mask) != (copyID & mask) - && !job_still_useful(job)) + && !job_still_alive(job)) { /* Free the channel and ordinary items it contains, but don't * recurse into Lists, Dictionaries etc. */ @@ -4523,7 +4561,7 @@ free_unused_jobs(int copyID, int mask) { job_next = job->jv_next; if ((job->jv_copyID & mask) != (copyID & mask) - && !job_still_useful(job)) + && !job_still_alive(job)) { /* Free the job struct itself. */ job_free_job(job); @@ -4614,34 +4652,31 @@ has_pending_job(void) job_T *job; for (job = first_job; job != NULL; job = job->jv_next) - if (job->jv_status == JOB_STARTED && job_still_useful(job)) + if (job_still_alive(job)) return TRUE; return FALSE; } +#define MAX_CHECK_ENDED 8 + /* * Called once in a while: check if any jobs that seem useful have ended. */ void job_check_ended(void) { - static time_t last_check = 0; - time_t now; - job_T *job; - job_T *next; - - /* Only do this once in 10 seconds. */ - now = time(NULL); - if (last_check + 10 < now) + int i; + + for (i = 0; i < MAX_CHECK_ENDED; ++i) { - last_check = now; - for (job = first_job; job != NULL; job = next) - { - next = job->jv_next; - if (job->jv_status == JOB_STARTED && job_still_useful(job)) - job_status(job); /* may free "job" */ - } + job_T *job = mch_detect_ended_job(first_job); + + if (job == NULL) + break; + if (job_still_useful(job)) + job_cleanup(job); /* may free "job" */ } + if (channel_need_redraw) { channel_need_redraw = FALSE; @@ -4862,32 +4897,7 @@ job_status(job_T *job) { result = mch_job_status(job); if (job->jv_status == JOB_ENDED) - ch_log(job->jv_channel, "Job ended"); - if (job->jv_status == JOB_ENDED && job->jv_exit_cb != NULL) - { - typval_T argv[3]; - typval_T rettv; - int dummy; - - /* invoke the exit callback; make sure the refcount is > 0 */ - ++job->jv_refcount; - argv[0].v_type = VAR_JOB; - argv[0].vval.v_job = job; - argv[1].v_type = VAR_NUMBER; - argv[1].vval.v_number = job->jv_exitval; - call_func(job->jv_exit_cb, (int)STRLEN(job->jv_exit_cb), - &rettv, 2, argv, NULL, 0L, 0L, &dummy, TRUE, - job->jv_exit_partial, NULL); - clear_tv(&rettv); - --job->jv_refcount; - channel_need_redraw = TRUE; - } - if (job->jv_status == JOB_ENDED && job->jv_refcount == 0) - { - /* The job was already unreferenced, now that it ended it can be - * freed. Careful: caller must not use "job" after this! */ - job_free(job); - } + job_cleanup(job); } return result; } diff --git a/src/os_unix.c b/src/os_unix.c --- a/src/os_unix.c +++ b/src/os_unix.c @@ -5294,8 +5294,7 @@ mch_job_status(job_T *job) if (wait_pid == -1) { /* process must have exited */ - job->jv_status = JOB_ENDED; - return "dead"; + goto return_dead; } if (wait_pid == 0) return "run"; @@ -5303,16 +5302,62 @@ mch_job_status(job_T *job) { /* LINTED avoid "bitwise operation on signed value" */ job->jv_exitval = WEXITSTATUS(status); - job->jv_status = JOB_ENDED; - return "dead"; + goto return_dead; } if (WIFSIGNALED(status)) { job->jv_exitval = -1; - job->jv_status = JOB_ENDED; - return "dead"; + goto return_dead; } return "run"; + +return_dead: + if (job->jv_status != JOB_ENDED) + { + ch_log(job->jv_channel, "Job ended"); + job->jv_status = JOB_ENDED; + } + return "dead"; +} + + job_T * +mch_detect_ended_job(job_T *job_list) +{ +# ifdef HAVE_UNION_WAIT + union wait status; +# else + int status = -1; +# endif + pid_t wait_pid = 0; + job_T *job; + +# ifdef __NeXT__ + wait_pid = wait4(-1, &status, WNOHANG, (struct rusage *)0); +# else + wait_pid = waitpid(-1, &status, WNOHANG); +# endif + if (wait_pid <= 0) + /* no process ended */ + return NULL; + for (job = job_list; job != NULL; job = job->jv_next) + { + if (job->jv_pid == wait_pid) + { + if (WIFEXITED(status)) + /* LINTED avoid "bitwise operation on signed value" */ + job->jv_exitval = WEXITSTATUS(status); + else if (WIFSIGNALED(status)) + job->jv_exitval = -1; + if (job->jv_status != JOB_ENDED) + { + ch_log(job->jv_channel, "Job ended"); + job->jv_status = JOB_ENDED; + } + return job; + } + } + return NULL; + } int diff --git a/src/os_win32.c b/src/os_win32.c --- a/src/os_win32.c +++ b/src/os_win32.c @@ -4973,13 +4973,53 @@ mch_job_status(job_T *job) if (!GetExitCodeProcess(job->jv_proc_info.hProcess, &dwExitCode) || dwExitCode != STILL_ACTIVE) { - job->jv_status = JOB_ENDED; job->jv_exitval = (int)dwExitCode; + if (job->jv_status != JOB_ENDED) + { + ch_log(job->jv_channel, "Job ended"); + job->jv_status = JOB_ENDED; + } return "dead"; } return "run"; } + job_T * +mch_detect_ended_job(job_T *job_list) +{ + HANDLE jobHandles[MAXIMUM_WAIT_OBJECTS]; + job_T *jobArray[MAXIMUM_WAIT_OBJECTS]; + job_T *job = job_list; + + while (job != NULL) + { + DWORD n; + DWORD result; + + for (n = 0; n < MAXIMUM_WAIT_OBJECTS + && job != NULL; job = job->jv_next) + { + if (job->jv_status == JOB_STARTED) + { + jobHandles[n] = job->jv_proc_info.hProcess; + jobArray[n] = job; + ++n; + } + } + if (n == 0) + continue; + result = WaitForMultipleObjects(n, jobHandles, FALSE, 0); + if (result >= WAIT_OBJECT_0 && result < WAIT_OBJECT_0 + n) + { + job_T *wait_job = jobArray[result - WAIT_OBJECT_0]; + + if (STRCMP(mch_job_status(wait_job), "dead") == 0) + return wait_job; + } + } + return NULL; +} + int mch_stop_job(job_T *job, char_u *how) { diff --git a/src/proto/os_unix.pro b/src/proto/os_unix.pro --- a/src/proto/os_unix.pro +++ b/src/proto/os_unix.pro @@ -59,6 +59,7 @@ int mch_parse_cmd(char_u *cmd, int use_s int mch_call_shell(char_u *cmd, int options); void mch_start_job(char **argv, job_T *job, jobopt_T *options); char *mch_job_status(job_T *job); +job_T *mch_detect_ended_job(job_T *job_list); int mch_stop_job(job_T *job, char_u *how); void mch_clear_job(job_T *job); void mch_breakcheck(int force); diff --git a/src/proto/os_win32.pro b/src/proto/os_win32.pro --- a/src/proto/os_win32.pro +++ b/src/proto/os_win32.pro @@ -41,6 +41,7 @@ void mch_set_winsize_now(void); int mch_call_shell(char_u *cmd, int options); void mch_start_job(char *cmd, job_T *job, jobopt_T *options); char *mch_job_status(job_T *job); +job_T *mch_detect_ended_job(job_T *job_list); int mch_stop_job(job_T *job, char_u *how); void mch_clear_job(job_T *job); void mch_set_normal_colors(void); 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,6 +1362,24 @@ 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) +endfunction + +func Test_exit_callback_interval() + if !has('job') + return + endif + + let g:exit_cb_time.start = reltime() + 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) + call assert_true(elapsed < 1.0) +endfunc + """"""""" let g:Ch_close_ret = 'alive' 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 */ /**/ + 36, +/**/ 35, /**/ 34,