forked from aniani/vim
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)
This commit is contained in:
108
src/channel.c
108
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)
|
#if defined(EXITFREE) || defined(PROTO)
|
||||||
void
|
void
|
||||||
job_free_all(void)
|
job_free_all(void)
|
||||||
@@ -4445,10 +4478,15 @@ job_free_all(void)
|
|||||||
static int
|
static int
|
||||||
job_still_useful(job_T *job)
|
job_still_useful(job_T *job)
|
||||||
{
|
{
|
||||||
return job->jv_status == JOB_STARTED
|
return (job->jv_stoponexit != NULL || job->jv_exit_cb != NULL
|
||||||
&& (job->jv_stoponexit != NULL || job->jv_exit_cb != NULL
|
|| (job->jv_channel != NULL
|
||||||
|| (job->jv_channel != NULL
|
&& channel_still_useful(job->jv_channel)));
|
||||||
&& 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;
|
typval_T tv;
|
||||||
|
|
||||||
for (job = first_job; job != NULL; job = job->jv_next)
|
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.v_type = VAR_JOB;
|
||||||
tv.vval.v_job = 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
|
/* Do not free the job when it has not ended yet and there is a
|
||||||
* "stoponexit" flag or an exit callback. */
|
* "stoponexit" flag or an exit callback. */
|
||||||
if (!job_still_useful(job))
|
if (!job_still_alive(job))
|
||||||
{
|
{
|
||||||
job_free(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)
|
for (job = first_job; job != NULL; job = job->jv_next)
|
||||||
if ((job->jv_copyID & mask) != (copyID & mask)
|
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
|
/* Free the channel and ordinary items it contains, but don't
|
||||||
* recurse into Lists, Dictionaries etc. */
|
* recurse into Lists, Dictionaries etc. */
|
||||||
@@ -4523,7 +4561,7 @@ free_unused_jobs(int copyID, int mask)
|
|||||||
{
|
{
|
||||||
job_next = job->jv_next;
|
job_next = job->jv_next;
|
||||||
if ((job->jv_copyID & mask) != (copyID & mask)
|
if ((job->jv_copyID & mask) != (copyID & mask)
|
||||||
&& !job_still_useful(job))
|
&& !job_still_alive(job))
|
||||||
{
|
{
|
||||||
/* Free the job struct itself. */
|
/* Free the job struct itself. */
|
||||||
job_free_job(job);
|
job_free_job(job);
|
||||||
@@ -4614,34 +4652,31 @@ has_pending_job(void)
|
|||||||
job_T *job;
|
job_T *job;
|
||||||
|
|
||||||
for (job = first_job; job != NULL; job = job->jv_next)
|
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 TRUE;
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define MAX_CHECK_ENDED 8
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Called once in a while: check if any jobs that seem useful have ended.
|
* Called once in a while: check if any jobs that seem useful have ended.
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
job_check_ended(void)
|
job_check_ended(void)
|
||||||
{
|
{
|
||||||
static time_t last_check = 0;
|
int i;
|
||||||
time_t now;
|
|
||||||
job_T *job;
|
|
||||||
job_T *next;
|
|
||||||
|
|
||||||
/* Only do this once in 10 seconds. */
|
for (i = 0; i < MAX_CHECK_ENDED; ++i)
|
||||||
now = time(NULL);
|
|
||||||
if (last_check + 10 < now)
|
|
||||||
{
|
{
|
||||||
last_check = now;
|
job_T *job = mch_detect_ended_job(first_job);
|
||||||
for (job = first_job; job != NULL; job = next)
|
|
||||||
{
|
if (job == NULL)
|
||||||
next = job->jv_next;
|
break;
|
||||||
if (job->jv_status == JOB_STARTED && job_still_useful(job))
|
if (job_still_useful(job))
|
||||||
job_status(job); /* may free "job" */
|
job_cleanup(job); /* may free "job" */
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (channel_need_redraw)
|
if (channel_need_redraw)
|
||||||
{
|
{
|
||||||
channel_need_redraw = FALSE;
|
channel_need_redraw = FALSE;
|
||||||
@@ -4862,32 +4897,7 @@ job_status(job_T *job)
|
|||||||
{
|
{
|
||||||
result = mch_job_status(job);
|
result = mch_job_status(job);
|
||||||
if (job->jv_status == JOB_ENDED)
|
if (job->jv_status == JOB_ENDED)
|
||||||
ch_log(job->jv_channel, "Job ended");
|
job_cleanup(job);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@@ -5294,8 +5294,7 @@ mch_job_status(job_T *job)
|
|||||||
if (wait_pid == -1)
|
if (wait_pid == -1)
|
||||||
{
|
{
|
||||||
/* process must have exited */
|
/* process must have exited */
|
||||||
job->jv_status = JOB_ENDED;
|
goto return_dead;
|
||||||
return "dead";
|
|
||||||
}
|
}
|
||||||
if (wait_pid == 0)
|
if (wait_pid == 0)
|
||||||
return "run";
|
return "run";
|
||||||
@@ -5303,16 +5302,62 @@ mch_job_status(job_T *job)
|
|||||||
{
|
{
|
||||||
/* LINTED avoid "bitwise operation on signed value" */
|
/* LINTED avoid "bitwise operation on signed value" */
|
||||||
job->jv_exitval = WEXITSTATUS(status);
|
job->jv_exitval = WEXITSTATUS(status);
|
||||||
job->jv_status = JOB_ENDED;
|
goto return_dead;
|
||||||
return "dead";
|
|
||||||
}
|
}
|
||||||
if (WIFSIGNALED(status))
|
if (WIFSIGNALED(status))
|
||||||
{
|
{
|
||||||
job->jv_exitval = -1;
|
job->jv_exitval = -1;
|
||||||
job->jv_status = JOB_ENDED;
|
goto return_dead;
|
||||||
return "dead";
|
|
||||||
}
|
}
|
||||||
return "run";
|
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
|
int
|
||||||
|
@@ -4973,13 +4973,53 @@ mch_job_status(job_T *job)
|
|||||||
if (!GetExitCodeProcess(job->jv_proc_info.hProcess, &dwExitCode)
|
if (!GetExitCodeProcess(job->jv_proc_info.hProcess, &dwExitCode)
|
||||||
|| dwExitCode != STILL_ACTIVE)
|
|| dwExitCode != STILL_ACTIVE)
|
||||||
{
|
{
|
||||||
job->jv_status = JOB_ENDED;
|
|
||||||
job->jv_exitval = (int)dwExitCode;
|
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 "dead";
|
||||||
}
|
}
|
||||||
return "run";
|
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
|
int
|
||||||
mch_stop_job(job_T *job, char_u *how)
|
mch_stop_job(job_T *job, char_u *how)
|
||||||
{
|
{
|
||||||
|
@@ -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);
|
int mch_call_shell(char_u *cmd, int options);
|
||||||
void mch_start_job(char **argv, job_T *job, jobopt_T *options);
|
void mch_start_job(char **argv, job_T *job, jobopt_T *options);
|
||||||
char *mch_job_status(job_T *job);
|
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);
|
int mch_stop_job(job_T *job, char_u *how);
|
||||||
void mch_clear_job(job_T *job);
|
void mch_clear_job(job_T *job);
|
||||||
void mch_breakcheck(int force);
|
void mch_breakcheck(int force);
|
||||||
|
@@ -41,6 +41,7 @@ void mch_set_winsize_now(void);
|
|||||||
int mch_call_shell(char_u *cmd, int options);
|
int mch_call_shell(char_u *cmd, int options);
|
||||||
void mch_start_job(char *cmd, job_T *job, jobopt_T *options);
|
void mch_start_job(char *cmd, job_T *job, jobopt_T *options);
|
||||||
char *mch_job_status(job_T *job);
|
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);
|
int mch_stop_job(job_T *job, char_u *how);
|
||||||
void mch_clear_job(job_T *job);
|
void mch_clear_job(job_T *job);
|
||||||
void mch_set_normal_colors(void);
|
void mch_set_normal_colors(void);
|
||||||
|
@@ -1362,6 +1362,24 @@ func Test_exit_callback()
|
|||||||
endif
|
endif
|
||||||
endfunc
|
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'
|
let g:Ch_close_ret = 'alive'
|
||||||
|
@@ -764,6 +764,8 @@ static char *(features[]) =
|
|||||||
|
|
||||||
static int included_patches[] =
|
static int included_patches[] =
|
||||||
{ /* Add new patch number below this line */
|
{ /* Add new patch number below this line */
|
||||||
|
/**/
|
||||||
|
36,
|
||||||
/**/
|
/**/
|
||||||
35,
|
35,
|
||||||
/**/
|
/**/
|
||||||
|
Reference in New Issue
Block a user