changeset 10279:c5c15c818bda v8.0.0036

commit https://github.com/vim/vim/commit/97792de2762cc79cc365a8a0b858f27753179577 Author: Bram Moolenaar <Bram@vim.org> 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)
author Christian Brabandt <cb@256bit.org>
date Sat, 15 Oct 2016 18:45:04 +0200
parents abb9c83e2b7c
children 511f213d76aa
files src/channel.c src/os_unix.c src/os_win32.c src/proto/os_unix.pro src/proto/os_win32.pro src/testdir/test_channel.vim src/version.c
diffstat 7 files changed, 174 insertions(+), 57 deletions(-) [+]
line wrap: on
line diff
--- 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;
 }
--- 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
--- 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)
 {
--- 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);
--- 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);
--- 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'
--- 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,