Skip to content

Commit 97792de

Browse files
committed
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)
1 parent 472e859 commit 97792de

7 files changed

Lines changed: 173 additions & 56 deletions

File tree

src/channel.c

Lines changed: 59 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -4428,6 +4428,39 @@ job_free(job_T *job)
44284428
}
44294429
}
44304430

4431+
static void
4432+
job_cleanup(job_T *job)
4433+
{
4434+
if (job->jv_status != JOB_ENDED)
4435+
return;
4436+
4437+
if (job->jv_exit_cb != NULL)
4438+
{
4439+
typval_T argv[3];
4440+
typval_T rettv;
4441+
int dummy;
4442+
4443+
/* invoke the exit callback; make sure the refcount is > 0 */
4444+
++job->jv_refcount;
4445+
argv[0].v_type = VAR_JOB;
4446+
argv[0].vval.v_job = job;
4447+
argv[1].v_type = VAR_NUMBER;
4448+
argv[1].vval.v_number = job->jv_exitval;
4449+
call_func(job->jv_exit_cb, (int)STRLEN(job->jv_exit_cb),
4450+
&rettv, 2, argv, NULL, 0L, 0L, &dummy, TRUE,
4451+
job->jv_exit_partial, NULL);
4452+
clear_tv(&rettv);
4453+
--job->jv_refcount;
4454+
channel_need_redraw = TRUE;
4455+
}
4456+
if (job->jv_refcount == 0)
4457+
{
4458+
/* The job was already unreferenced, now that it ended it can be
4459+
* freed. Careful: caller must not use "job" after this! */
4460+
job_free(job);
4461+
}
4462+
}
4463+
44314464
#if defined(EXITFREE) || defined(PROTO)
44324465
void
44334466
job_free_all(void)
@@ -4445,10 +4478,15 @@ job_free_all(void)
44454478
static int
44464479
job_still_useful(job_T *job)
44474480
{
4448-
return job->jv_status == JOB_STARTED
4449-
&& (job->jv_stoponexit != NULL || job->jv_exit_cb != NULL
4450-
|| (job->jv_channel != NULL
4451-
&& channel_still_useful(job->jv_channel)));
4481+
return (job->jv_stoponexit != NULL || job->jv_exit_cb != NULL
4482+
|| (job->jv_channel != NULL
4483+
&& channel_still_useful(job->jv_channel)));
4484+
}
4485+
4486+
static int
4487+
job_still_alive(job_T *job)
4488+
{
4489+
return (job->jv_status == JOB_STARTED) && job_still_useful(job);
44524490
}
44534491

44544492
/*
@@ -4462,7 +4500,7 @@ set_ref_in_job(int copyID)
44624500
typval_T tv;
44634501

44644502
for (job = first_job; job != NULL; job = job->jv_next)
4465-
if (job_still_useful(job))
4503+
if (job_still_alive(job))
44664504
{
44674505
tv.v_type = VAR_JOB;
44684506
tv.vval.v_job = job;
@@ -4478,7 +4516,7 @@ job_unref(job_T *job)
44784516
{
44794517
/* Do not free the job when it has not ended yet and there is a
44804518
* "stoponexit" flag or an exit callback. */
4481-
if (!job_still_useful(job))
4519+
if (!job_still_alive(job))
44824520
{
44834521
job_free(job);
44844522
}
@@ -4503,7 +4541,7 @@ free_unused_jobs_contents(int copyID, int mask)
45034541

45044542
for (job = first_job; job != NULL; job = job->jv_next)
45054543
if ((job->jv_copyID & mask) != (copyID & mask)
4506-
&& !job_still_useful(job))
4544+
&& !job_still_alive(job))
45074545
{
45084546
/* Free the channel and ordinary items it contains, but don't
45094547
* recurse into Lists, Dictionaries etc. */
@@ -4523,7 +4561,7 @@ free_unused_jobs(int copyID, int mask)
45234561
{
45244562
job_next = job->jv_next;
45254563
if ((job->jv_copyID & mask) != (copyID & mask)
4526-
&& !job_still_useful(job))
4564+
&& !job_still_alive(job))
45274565
{
45284566
/* Free the job struct itself. */
45294567
job_free_job(job);
@@ -4614,34 +4652,31 @@ has_pending_job(void)
46144652
job_T *job;
46154653

46164654
for (job = first_job; job != NULL; job = job->jv_next)
4617-
if (job->jv_status == JOB_STARTED && job_still_useful(job))
4655+
if (job_still_alive(job))
46184656
return TRUE;
46194657
return FALSE;
46204658
}
46214659

4660+
#define MAX_CHECK_ENDED 8
4661+
46224662
/*
46234663
* Called once in a while: check if any jobs that seem useful have ended.
46244664
*/
46254665
void
46264666
job_check_ended(void)
46274667
{
4628-
static time_t last_check = 0;
4629-
time_t now;
4630-
job_T *job;
4631-
job_T *next;
4668+
int i;
46324669

4633-
/* Only do this once in 10 seconds. */
4634-
now = time(NULL);
4635-
if (last_check + 10 < now)
4670+
for (i = 0; i < MAX_CHECK_ENDED; ++i)
46364671
{
4637-
last_check = now;
4638-
for (job = first_job; job != NULL; job = next)
4639-
{
4640-
next = job->jv_next;
4641-
if (job->jv_status == JOB_STARTED && job_still_useful(job))
4642-
job_status(job); /* may free "job" */
4643-
}
4672+
job_T *job = mch_detect_ended_job(first_job);
4673+
4674+
if (job == NULL)
4675+
break;
4676+
if (job_still_useful(job))
4677+
job_cleanup(job); /* may free "job" */
46444678
}
4679+
46454680
if (channel_need_redraw)
46464681
{
46474682
channel_need_redraw = FALSE;
@@ -4862,32 +4897,7 @@ job_status(job_T *job)
48624897
{
48634898
result = mch_job_status(job);
48644899
if (job->jv_status == JOB_ENDED)
4865-
ch_log(job->jv_channel, "Job ended");
4866-
if (job->jv_status == JOB_ENDED && job->jv_exit_cb != NULL)
4867-
{
4868-
typval_T argv[3];
4869-
typval_T rettv;
4870-
int dummy;
4871-
4872-
/* invoke the exit callback; make sure the refcount is > 0 */
4873-
++job->jv_refcount;
4874-
argv[0].v_type = VAR_JOB;
4875-
argv[0].vval.v_job = job;
4876-
argv[1].v_type = VAR_NUMBER;
4877-
argv[1].vval.v_number = job->jv_exitval;
4878-
call_func(job->jv_exit_cb, (int)STRLEN(job->jv_exit_cb),
4879-
&rettv, 2, argv, NULL, 0L, 0L, &dummy, TRUE,
4880-
job->jv_exit_partial, NULL);
4881-
clear_tv(&rettv);
4882-
--job->jv_refcount;
4883-
channel_need_redraw = TRUE;
4884-
}
4885-
if (job->jv_status == JOB_ENDED && job->jv_refcount == 0)
4886-
{
4887-
/* The job was already unreferenced, now that it ended it can be
4888-
* freed. Careful: caller must not use "job" after this! */
4889-
job_free(job);
4890-
}
4900+
job_cleanup(job);
48914901
}
48924902
return result;
48934903
}

src/os_unix.c

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5294,25 +5294,70 @@ mch_job_status(job_T *job)
52945294
if (wait_pid == -1)
52955295
{
52965296
/* process must have exited */
5297-
job->jv_status = JOB_ENDED;
5298-
return "dead";
5297+
goto return_dead;
52995298
}
53005299
if (wait_pid == 0)
53015300
return "run";
53025301
if (WIFEXITED(status))
53035302
{
53045303
/* LINTED avoid "bitwise operation on signed value" */
53055304
job->jv_exitval = WEXITSTATUS(status);
5306-
job->jv_status = JOB_ENDED;
5307-
return "dead";
5305+
goto return_dead;
53085306
}
53095307
if (WIFSIGNALED(status))
53105308
{
53115309
job->jv_exitval = -1;
5312-
job->jv_status = JOB_ENDED;
5313-
return "dead";
5310+
goto return_dead;
53145311
}
53155312
return "run";
5313+
5314+
return_dead:
5315+
if (job->jv_status != JOB_ENDED)
5316+
{
5317+
ch_log(job->jv_channel, "Job ended");
5318+
job->jv_status = JOB_ENDED;
5319+
}
5320+
return "dead";
5321+
}
5322+
5323+
job_T *
5324+
mch_detect_ended_job(job_T *job_list)
5325+
{
5326+
# ifdef HAVE_UNION_WAIT
5327+
union wait status;
5328+
# else
5329+
int status = -1;
5330+
# endif
5331+
pid_t wait_pid = 0;
5332+
job_T *job;
5333+
5334+
# ifdef __NeXT__
5335+
wait_pid = wait4(-1, &status, WNOHANG, (struct rusage *)0);
5336+
# else
5337+
wait_pid = waitpid(-1, &status, WNOHANG);
5338+
# endif
5339+
if (wait_pid <= 0)
5340+
/* no process ended */
5341+
return NULL;
5342+
for (job = job_list; job != NULL; job = job->jv_next)
5343+
{
5344+
if (job->jv_pid == wait_pid)
5345+
{
5346+
if (WIFEXITED(status))
5347+
/* LINTED avoid "bitwise operation on signed value" */
5348+
job->jv_exitval = WEXITSTATUS(status);
5349+
else if (WIFSIGNALED(status))
5350+
job->jv_exitval = -1;
5351+
if (job->jv_status != JOB_ENDED)
5352+
{
5353+
ch_log(job->jv_channel, "Job ended");
5354+
job->jv_status = JOB_ENDED;
5355+
}
5356+
return job;
5357+
}
5358+
}
5359+
return NULL;
5360+
53165361
}
53175362

53185363
int

src/os_win32.c

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4973,13 +4973,53 @@ mch_job_status(job_T *job)
49734973
if (!GetExitCodeProcess(job->jv_proc_info.hProcess, &dwExitCode)
49744974
|| dwExitCode != STILL_ACTIVE)
49754975
{
4976-
job->jv_status = JOB_ENDED;
49774976
job->jv_exitval = (int)dwExitCode;
4977+
if (job->jv_status != JOB_ENDED)
4978+
{
4979+
ch_log(job->jv_channel, "Job ended");
4980+
job->jv_status = JOB_ENDED;
4981+
}
49784982
return "dead";
49794983
}
49804984
return "run";
49814985
}
49824986

4987+
job_T *
4988+
mch_detect_ended_job(job_T *job_list)
4989+
{
4990+
HANDLE jobHandles[MAXIMUM_WAIT_OBJECTS];
4991+
job_T *jobArray[MAXIMUM_WAIT_OBJECTS];
4992+
job_T *job = job_list;
4993+
4994+
while (job != NULL)
4995+
{
4996+
DWORD n;
4997+
DWORD result;
4998+
4999+
for (n = 0; n < MAXIMUM_WAIT_OBJECTS
5000+
&& job != NULL; job = job->jv_next)
5001+
{
5002+
if (job->jv_status == JOB_STARTED)
5003+
{
5004+
jobHandles[n] = job->jv_proc_info.hProcess;
5005+
jobArray[n] = job;
5006+
++n;
5007+
}
5008+
}
5009+
if (n == 0)
5010+
continue;
5011+
result = WaitForMultipleObjects(n, jobHandles, FALSE, 0);
5012+
if (result >= WAIT_OBJECT_0 && result < WAIT_OBJECT_0 + n)
5013+
{
5014+
job_T *wait_job = jobArray[result - WAIT_OBJECT_0];
5015+
5016+
if (STRCMP(mch_job_status(wait_job), "dead") == 0)
5017+
return wait_job;
5018+
}
5019+
}
5020+
return NULL;
5021+
}
5022+
49835023
int
49845024
mch_stop_job(job_T *job, char_u *how)
49855025
{

src/proto/os_unix.pro

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ int mch_parse_cmd(char_u *cmd, int use_shcf, char ***argv, int *argc);
5959
int mch_call_shell(char_u *cmd, int options);
6060
void mch_start_job(char **argv, job_T *job, jobopt_T *options);
6161
char *mch_job_status(job_T *job);
62+
job_T *mch_detect_ended_job(job_T *job_list);
6263
int mch_stop_job(job_T *job, char_u *how);
6364
void mch_clear_job(job_T *job);
6465
void mch_breakcheck(int force);

src/proto/os_win32.pro

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ void mch_set_winsize_now(void);
4141
int mch_call_shell(char_u *cmd, int options);
4242
void mch_start_job(char *cmd, job_T *job, jobopt_T *options);
4343
char *mch_job_status(job_T *job);
44+
job_T *mch_detect_ended_job(job_T *job_list);
4445
int mch_stop_job(job_T *job, char_u *how);
4546
void mch_clear_job(job_T *job);
4647
void mch_set_normal_colors(void);

src/testdir/test_channel.vim

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1362,6 +1362,24 @@ func Test_exit_callback()
13621362
endif
13631363
endfunc
13641364

1365+
let g:exit_cb_time = {'start': 0, 'end': 0}
1366+
function MyExitTimeCb(job, status)
1367+
let g:exit_cb_time.end = reltime(g:exit_cb_time.start)
1368+
endfunction
1369+
1370+
func Test_exit_callback_interval()
1371+
if !has('job')
1372+
return
1373+
endif
1374+
1375+
let g:exit_cb_time.start = reltime()
1376+
let job = job_start([s:python, '-c', 'import time;time.sleep(0.5)'], {'exit_cb': 'MyExitTimeCb'})
1377+
call WaitFor('g:exit_cb_time.end != 0')
1378+
let elapsed = reltimefloat(g:exit_cb_time.end)
1379+
call assert_true(elapsed > 0.3)
1380+
call assert_true(elapsed < 1.0)
1381+
endfunc
1382+
13651383
"""""""""
13661384

13671385
let g:Ch_close_ret = 'alive'

src/version.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -764,6 +764,8 @@ static char *(features[]) =
764764

765765
static int included_patches[] =
766766
{ /* Add new patch number below this line */
767+
/**/
768+
36,
767769
/**/
768770
35,
769771
/**/

0 commit comments

Comments
 (0)