summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBram Moolenaar <Bram@vim.org>2016-10-15 18:36:49 +0200
committerBram Moolenaar <Bram@vim.org>2016-10-15 18:36:49 +0200
commit97792de2762cc79cc365a8a0b858f27753179577 (patch)
tree6a9b69f2cabc6135b91c7dfaed7c69a9a7c0419f
parent472e85970ee3a80abd824bef510df12e9cfe9e96 (diff)
downloadvim-97792de2762cc79cc365a8a0b858f27753179577.zip
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)
-rw-r--r--src/channel.c108
-rw-r--r--src/os_unix.c57
-rw-r--r--src/os_win32.c42
-rw-r--r--src/proto/os_unix.pro1
-rw-r--r--src/proto/os_win32.pro1
-rw-r--r--src/testdir/test_channel.vim18
-rw-r--r--src/version.c2
7 files changed, 173 insertions, 56 deletions
diff --git a/src/channel.c b/src/channel.c
index 2d68287a4..0bd23785e 100644
--- 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, 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;
}
diff --git a/src/os_unix.c b/src/os_unix.c
index 8b0dd552d..bc9acd4f7 100644
--- 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
index 9ff2f7ee4..d52beb886 100644
--- 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
index b1e9b8e29..700b69ef2 100644
--- 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_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);
diff --git a/src/proto/os_win32.pro b/src/proto/os_win32.pro
index b76e3474b..b21a7ba33 100644
--- 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
index fbcd496e9..e7ba66768 100644
--- 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
index 513223e45..61e6979d5 100644
--- 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,