mirror of
https://github.com/vim/vim.git
synced 2025-09-25 03:54:15 -04:00
patch 8.0.0087
Problem: When the channel callback gets job info the job may already have been deleted. (lifepillar) Solution: Do not delete the job when the channel is still useful. (ichizok, closes #1242, closes #1245)
This commit is contained in:
117
src/channel.c
117
src/channel.c
@@ -4433,19 +4433,66 @@ job_free(job_T *job)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if defined(EXITFREE) || defined(PROTO)
|
||||||
|
void
|
||||||
|
job_free_all(void)
|
||||||
|
{
|
||||||
|
while (first_job != NULL)
|
||||||
|
job_free(first_job);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Return TRUE if we need to check if the process of "job" has ended.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
job_need_end_check(job_T *job)
|
||||||
|
{
|
||||||
|
return job->jv_status == JOB_STARTED
|
||||||
|
&& (job->jv_stoponexit != NULL || job->jv_exit_cb != NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Return TRUE if the channel of "job" is still useful.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
job_channel_still_useful(job_T *job)
|
||||||
|
{
|
||||||
|
return job->jv_channel != NULL && channel_still_useful(job->jv_channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Return TRUE if the job should not be freed yet. Do not free the job when
|
||||||
|
* it has not ended yet and there is a "stoponexit" flag, an exit callback
|
||||||
|
* or when the associated channel will do something with the job output.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
job_still_useful(job_T *job)
|
||||||
|
{
|
||||||
|
return job_need_end_check(job) || job_channel_still_useful(job);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* NOTE: Must call job_cleanup() only once right after the status of "job"
|
||||||
|
* changed to JOB_ENDED (i.e. after job_status() returned "dead" first or
|
||||||
|
* mch_detect_ended_job() returned non-NULL).
|
||||||
|
*/
|
||||||
static void
|
static void
|
||||||
job_cleanup(job_T *job)
|
job_cleanup(job_T *job)
|
||||||
{
|
{
|
||||||
if (job->jv_status != JOB_ENDED)
|
if (job->jv_status != JOB_ENDED)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
/* Ready to cleanup the job. */
|
||||||
|
job->jv_status = JOB_FINISHED;
|
||||||
|
|
||||||
if (job->jv_exit_cb != NULL)
|
if (job->jv_exit_cb != NULL)
|
||||||
{
|
{
|
||||||
typval_T argv[3];
|
typval_T argv[3];
|
||||||
typval_T rettv;
|
typval_T rettv;
|
||||||
int dummy;
|
int dummy;
|
||||||
|
|
||||||
/* invoke the exit callback; make sure the refcount is > 0 */
|
/* Invoke the exit callback. Make sure the refcount is > 0. */
|
||||||
++job->jv_refcount;
|
++job->jv_refcount;
|
||||||
argv[0].v_type = VAR_JOB;
|
argv[0].v_type = VAR_JOB;
|
||||||
argv[0].vval.v_job = job;
|
argv[0].vval.v_job = job;
|
||||||
@@ -4458,42 +4505,18 @@ job_cleanup(job_T *job)
|
|||||||
--job->jv_refcount;
|
--job->jv_refcount;
|
||||||
channel_need_redraw = TRUE;
|
channel_need_redraw = TRUE;
|
||||||
}
|
}
|
||||||
if (job->jv_refcount == 0)
|
|
||||||
|
/* Do not free the job in case the close callback of the associated channel
|
||||||
|
* isn't invoked yet and may get information by job_info(). */
|
||||||
|
if (job->jv_refcount == 0 && !job_channel_still_useful(job))
|
||||||
{
|
{
|
||||||
/* The job was already unreferenced, now that it ended it can be
|
/* The job was already unreferenced and the associated channel was
|
||||||
* freed. Careful: caller must not use "job" after this! */
|
* detached, now that it ended it can be freed. Careful: caller must
|
||||||
|
* not use "job" after this! */
|
||||||
job_free(job);
|
job_free(job);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(EXITFREE) || defined(PROTO)
|
|
||||||
void
|
|
||||||
job_free_all(void)
|
|
||||||
{
|
|
||||||
while (first_job != NULL)
|
|
||||||
job_free(first_job);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Return TRUE if the job should not be freed yet. Do not free the job when
|
|
||||||
* it has not ended yet and there is a "stoponexit" flag, an exit callback
|
|
||||||
* or when the associated channel will do something with the job output.
|
|
||||||
*/
|
|
||||||
static int
|
|
||||||
job_still_useful(job_T *job)
|
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Mark references in jobs that are still useful.
|
* Mark references in jobs that are still useful.
|
||||||
*/
|
*/
|
||||||
@@ -4505,7 +4528,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_alive(job))
|
if (job_still_useful(job))
|
||||||
{
|
{
|
||||||
tv.v_type = VAR_JOB;
|
tv.v_type = VAR_JOB;
|
||||||
tv.vval.v_job = job;
|
tv.vval.v_job = job;
|
||||||
@@ -4514,19 +4537,25 @@ set_ref_in_job(int copyID)
|
|||||||
return abort;
|
return abort;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Dereference "job". Note that after this "job" may have been freed.
|
||||||
|
*/
|
||||||
void
|
void
|
||||||
job_unref(job_T *job)
|
job_unref(job_T *job)
|
||||||
{
|
{
|
||||||
if (job != NULL && --job->jv_refcount <= 0)
|
if (job != NULL && --job->jv_refcount <= 0)
|
||||||
|
{
|
||||||
|
/* Do not free the job if there is a channel where the close callback
|
||||||
|
* may get the job info. */
|
||||||
|
if (!job_channel_still_useful(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_alive(job))
|
if (!job_need_end_check(job))
|
||||||
{
|
{
|
||||||
job_free(job);
|
job_free(job);
|
||||||
}
|
}
|
||||||
else if (job->jv_channel != NULL
|
else if (job->jv_channel != NULL)
|
||||||
&& !channel_still_useful(job->jv_channel))
|
|
||||||
{
|
{
|
||||||
/* Do remove the link to the channel, otherwise it hangs
|
/* Do remove the link to the channel, otherwise it hangs
|
||||||
* around until Vim exits. See job_free() for refcount. */
|
* around until Vim exits. See job_free() for refcount. */
|
||||||
@@ -4537,6 +4566,7 @@ job_unref(job_T *job)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
free_unused_jobs_contents(int copyID, int mask)
|
free_unused_jobs_contents(int copyID, int mask)
|
||||||
@@ -4546,7 +4576,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_alive(job))
|
&& !job_still_useful(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. */
|
||||||
@@ -4566,7 +4596,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_alive(job))
|
&& !job_still_useful(job))
|
||||||
{
|
{
|
||||||
/* Free the job struct itself. */
|
/* Free the job struct itself. */
|
||||||
job_free_job(job);
|
job_free_job(job);
|
||||||
@@ -4660,8 +4690,7 @@ has_pending_job(void)
|
|||||||
/* Only should check if the channel has been closed, if the channel is
|
/* Only should check if the channel has been closed, if the channel is
|
||||||
* open the job won't exit. */
|
* open the job won't exit. */
|
||||||
if (job->jv_status == JOB_STARTED && job->jv_exit_cb != NULL
|
if (job->jv_status == JOB_STARTED && job->jv_exit_cb != NULL
|
||||||
&& (job->jv_channel == NULL
|
&& !job_channel_still_useful(job))
|
||||||
|| !channel_still_useful(job->jv_channel)))
|
|
||||||
return TRUE;
|
return TRUE;
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
@@ -4676,13 +4705,17 @@ job_check_ended(void)
|
|||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
|
if (first_job == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
for (i = 0; i < MAX_CHECK_ENDED; ++i)
|
for (i = 0; i < MAX_CHECK_ENDED; ++i)
|
||||||
{
|
{
|
||||||
|
/* NOTE: mch_detect_ended_job() must only return a job of which the
|
||||||
|
* status was just set to JOB_ENDED. */
|
||||||
job_T *job = mch_detect_ended_job(first_job);
|
job_T *job = mch_detect_ended_job(first_job);
|
||||||
|
|
||||||
if (job == NULL)
|
if (job == NULL)
|
||||||
break;
|
break;
|
||||||
if (job_still_useful(job))
|
|
||||||
job_cleanup(job); /* may free "job" */
|
job_cleanup(job); /* may free "job" */
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4897,7 +4930,7 @@ job_status(job_T *job)
|
|||||||
{
|
{
|
||||||
char *result;
|
char *result;
|
||||||
|
|
||||||
if (job->jv_status == JOB_ENDED)
|
if (job->jv_status >= JOB_ENDED)
|
||||||
/* No need to check, dead is dead. */
|
/* No need to check, dead is dead. */
|
||||||
result = "dead";
|
result = "dead";
|
||||||
else if (job->jv_status == JOB_FAILED)
|
else if (job->jv_status == JOB_FAILED)
|
||||||
|
@@ -7290,7 +7290,7 @@ get_tv_string_buf_chk(typval_T *varp, char_u *buf)
|
|||||||
if (job == NULL)
|
if (job == NULL)
|
||||||
return (char_u *)"no process";
|
return (char_u *)"no process";
|
||||||
status = job->jv_status == JOB_FAILED ? "fail"
|
status = job->jv_status == JOB_FAILED ? "fail"
|
||||||
: job->jv_status == JOB_ENDED ? "dead"
|
: job->jv_status >= JOB_ENDED ? "dead"
|
||||||
: "run";
|
: "run";
|
||||||
# ifdef UNIX
|
# ifdef UNIX
|
||||||
vim_snprintf((char *)buf, NUMBUFLEN,
|
vim_snprintf((char *)buf, NUMBUFLEN,
|
||||||
|
@@ -5354,7 +5354,7 @@ mch_job_status(job_T *job)
|
|||||||
return "run";
|
return "run";
|
||||||
|
|
||||||
return_dead:
|
return_dead:
|
||||||
if (job->jv_status != JOB_ENDED)
|
if (job->jv_status < JOB_ENDED)
|
||||||
{
|
{
|
||||||
ch_log(job->jv_channel, "Job ended");
|
ch_log(job->jv_channel, "Job ended");
|
||||||
job->jv_status = JOB_ENDED;
|
job->jv_status = JOB_ENDED;
|
||||||
@@ -5398,7 +5398,7 @@ mch_detect_ended_job(job_T *job_list)
|
|||||||
job->jv_exitval = WEXITSTATUS(status);
|
job->jv_exitval = WEXITSTATUS(status);
|
||||||
else if (WIFSIGNALED(status))
|
else if (WIFSIGNALED(status))
|
||||||
job->jv_exitval = -1;
|
job->jv_exitval = -1;
|
||||||
if (job->jv_status != JOB_ENDED)
|
if (job->jv_status < JOB_ENDED)
|
||||||
{
|
{
|
||||||
ch_log(job->jv_channel, "Job ended");
|
ch_log(job->jv_channel, "Job ended");
|
||||||
job->jv_status = JOB_ENDED;
|
job->jv_status = JOB_ENDED;
|
||||||
|
@@ -4978,7 +4978,7 @@ mch_job_status(job_T *job)
|
|||||||
|| dwExitCode != STILL_ACTIVE)
|
|| dwExitCode != STILL_ACTIVE)
|
||||||
{
|
{
|
||||||
job->jv_exitval = (int)dwExitCode;
|
job->jv_exitval = (int)dwExitCode;
|
||||||
if (job->jv_status != JOB_ENDED)
|
if (job->jv_status < JOB_ENDED)
|
||||||
{
|
{
|
||||||
ch_log(job->jv_channel, "Job ended");
|
ch_log(job->jv_channel, "Job ended");
|
||||||
job->jv_status = JOB_ENDED;
|
job->jv_status = JOB_ENDED;
|
||||||
|
@@ -1421,11 +1421,13 @@ struct partial_S
|
|||||||
dict_T *pt_dict; /* dict for "self" */
|
dict_T *pt_dict; /* dict for "self" */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* Status of a job. Order matters! */
|
||||||
typedef enum
|
typedef enum
|
||||||
{
|
{
|
||||||
JOB_FAILED,
|
JOB_FAILED,
|
||||||
JOB_STARTED,
|
JOB_STARTED,
|
||||||
JOB_ENDED
|
JOB_ENDED, /* detected job done */
|
||||||
|
JOB_FINISHED /* job done and cleanup done */
|
||||||
} jobstatus_T;
|
} jobstatus_T;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@@ -1232,6 +1232,32 @@ func Test_out_cb_lambda()
|
|||||||
endtry
|
endtry
|
||||||
endfunc
|
endfunc
|
||||||
|
|
||||||
|
func Test_close_and_exit_cb()
|
||||||
|
if !has('job')
|
||||||
|
return
|
||||||
|
endif
|
||||||
|
call ch_log('Test_close_and_exit_cb')
|
||||||
|
|
||||||
|
let dict = {'ret': {}}
|
||||||
|
func dict.close_cb(ch) dict
|
||||||
|
let self.ret['close_cb'] = job_status(ch_getjob(a:ch))
|
||||||
|
endfunc
|
||||||
|
func dict.exit_cb(job, status) dict
|
||||||
|
let self.ret['exit_cb'] = job_status(a:job)
|
||||||
|
endfunc
|
||||||
|
|
||||||
|
let g:job = job_start('echo', {
|
||||||
|
\ 'close_cb': dict.close_cb,
|
||||||
|
\ 'exit_cb': dict.exit_cb,
|
||||||
|
\ })
|
||||||
|
call assert_equal('run', job_status(g:job))
|
||||||
|
unlet g:job
|
||||||
|
call WaitFor('len(dict.ret) >= 2')
|
||||||
|
call assert_equal(2, len(dict.ret))
|
||||||
|
call assert_match('^\%(dead\|run\)', dict.ret['close_cb'])
|
||||||
|
call assert_equal('dead', dict.ret['exit_cb'])
|
||||||
|
endfunc
|
||||||
|
|
||||||
""""""""""
|
""""""""""
|
||||||
|
|
||||||
let g:Ch_unletResponse = ''
|
let g:Ch_unletResponse = ''
|
||||||
|
@@ -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 */
|
||||||
|
/**/
|
||||||
|
87,
|
||||||
/**/
|
/**/
|
||||||
86,
|
86,
|
||||||
/**/
|
/**/
|
||||||
|
Reference in New Issue
Block a user