]> granicus.if.org Git - vim/commitdiff
patch 8.0.0036 v8.0.0036
authorBram Moolenaar <Bram@vim.org>
Sat, 15 Oct 2016 16:36:49 +0000 (18:36 +0200)
committerBram Moolenaar <Bram@vim.org>
Sat, 15 Oct 2016 16:36:49 +0000 (18:36 +0200)
Problem:    Detecting that a job has finished may take a while.
Solution:   Check for a finished job more often (Ozaki Kiichi)

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

index 2d68287a4594cf6784675f5cec2ea59532b195f4..0bd23785e73ffc29e0c9bf1105b712f7dd56ddba 100644 (file)
@@ -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, int mask)
 
     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;
+    int                i;
 
-    /* Only do this once in 10 seconds. */
-    now = time(NULL);
-    if (last_check + 10 < now)
+    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;
 }
index 8b0dd552d15a6fc778b101ee8613acbc2e618f9e..bc9acd4f7b1b6a628fda43a28f9acc3b76a3a93c 100644 (file)
@@ -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
index 9ff2f7ee40a61ab16dcf5dc464e293276b03f9d8..d52beb8869a5add503f6cef2841bcc3ceaf85e3d 100644 (file)
@@ -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)
 {
index b1e9b8e299975b30922fd0bd4ed44baf1b95f62a..700b69ef2e56f30bc8dae78a9e153069dfde010a 100644 (file)
@@ -59,6 +59,7 @@ int mch_parse_cmd(char_u *cmd, int use_shcf, char ***argv, int *argc);
 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);
index b76e3474ba672feb764d38561a4163dfdc1ce511..b21a7ba339b0cdf1343a313b6212677d57cd1aa2 100644 (file)
@@ -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);
index fbcd496e99070d514232770603c530c7ddd9fc03..e7ba6676801991fbe8ee904e5d0c4c77def12b5b 100644 (file)
@@ -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'
index 513223e4508cd054170e88f022e68f7c1bfcbd21..61e6979d5fc3be8cf1673f17bf980a8e4dc2b867 100644 (file)
@@ -764,6 +764,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    36,
 /**/
     35,
 /**/