Skip to content

Commit

Permalink
patch 8.0.0036
Browse files Browse the repository at this point in the history
Problem:    Detecting that a job has finished may take a while.
Solution:   Check for a finished job more often (Ozaki Kiichi)
  • Loading branch information
brammool committed Oct 15, 2016
1 parent 472e859 commit 97792de
Show file tree
Hide file tree
Showing 7 changed files with 173 additions and 56 deletions.
108 changes: 59 additions & 49 deletions src/channel.c
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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);
} }


/* /*
Expand All @@ -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;
Expand All @@ -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);
} }
Expand All @@ -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. */
Expand All @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
} }
Expand Down
57 changes: 51 additions & 6 deletions src/os_unix.c
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -5294,25 +5294,70 @@ 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";
if (WIFEXITED(status)) if (WIFEXITED(status))
{ {
/* 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
Expand Down
42 changes: 41 additions & 1 deletion src/os_win32.c
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -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)
{ {
Expand Down
1 change: 1 addition & 0 deletions src/proto/os_unix.pro
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions src/proto/os_win32.pro
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -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);
Expand Down
18 changes: 18 additions & 0 deletions src/testdir/test_channel.vim
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -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'
Expand Down
2 changes: 2 additions & 0 deletions src/version.c
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -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,
/**/ /**/
Expand Down

0 comments on commit 97792de

Please sign in to comment.