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)
Files: src/channel.c, src/eval.c, src/os_unix.c, src/os_win32.c,
src/structs.h, src/testdir/test_channel.vim
*** ../vim-8.0.0086/src/channel.c 2016-11-12 21:12:48.538182233 +0100
--- src/channel.c 2016-11-17 17:13:15.080933875 +0100
***************
*** 4433,4451 ****
}
}
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;
--- 4433,4498 ----
}
}
+ #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
job_cleanup(job_T *job)
{
if (job->jv_status != JOB_ENDED)
return;
+ /* Ready to cleanup the job. */
+ job->jv_status = JOB_FINISHED;
+
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;
***************
*** 4458,4499 ****
--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)
- {
- 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.
*/
--- 4505,4522 ----
--job->jv_refcount;
channel_need_redraw = TRUE;
}
!
! /* 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 and the associated channel was
! * detached, now that it ended it can be freed. Careful: caller must
! * not use "job" after this! */
job_free(job);
}
}
/*
* Mark references in jobs that are still useful.
*/
***************
*** 4505,4511 ****
typval_T tv;
for (job = first_job; job != NULL; job = job->jv_next)
! if (job_still_alive(job))
{
tv.v_type = VAR_JOB;
tv.vval.v_job = job;
--- 4528,4534 ----
typval_T tv;
for (job = first_job; job != NULL; job = job->jv_next)
! if (job_still_useful(job))
{
tv.v_type = VAR_JOB;
tv.vval.v_job = job;
***************
*** 4514,4539 ****
return abort;
}
void
job_unref(job_T *job)
{
if (job != NULL && --job->jv_refcount <= 0)
{
! /* Do not free the job when it has not ended yet and there is a
! * "stoponexit" flag or an exit callback. */
! if (!job_still_alive(job))
! {
! job_free(job);
! }
! else if (job->jv_channel != NULL
! && !channel_still_useful(job->jv_channel))
! {
! /* Do remove the link to the channel, otherwise it hangs
! * around until Vim exits. See job_free() for refcount. */
! ch_log(job->jv_channel, "detaching channel from job");
! job->jv_channel->ch_job = NULL;
! channel_unref(job->jv_channel);
! job->jv_channel = NULL;
}
}
}
--- 4537,4569 ----
return abort;
}
+ /*
+ * Dereference "job". Note that after this "job" may have been freed.
+ */
void
job_unref(job_T *job)
{
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
! * "stoponexit" flag or an exit callback. */
! if (!job_need_end_check(job))
! {
! job_free(job);
! }
! else if (job->jv_channel != NULL)
! {
! /* Do remove the link to the channel, otherwise it hangs
! * around until Vim exits. See job_free() for refcount. */
! ch_log(job->jv_channel, "detaching channel from job");
! job->jv_channel->ch_job = NULL;
! channel_unref(job->jv_channel);
! job->jv_channel = NULL;
! }
}
}
}
***************
*** 4546,4552 ****
for (job = first_job; job != NULL; job = job->jv_next)
if ((job->jv_copyID & mask) != (copyID & mask)
! && !job_still_alive(job))
{
/* Free the channel and ordinary items it contains, but don't
* recurse into Lists, Dictionaries etc. */
--- 4576,4582 ----
for (job = first_job; job != NULL; job = job->jv_next)
if ((job->jv_copyID & mask) != (copyID & mask)
! && !job_still_useful(job))
{
/* Free the channel and ordinary items it contains, but don't
* recurse into Lists, Dictionaries etc. */
***************
*** 4566,4572 ****
{
job_next = job->jv_next;
if ((job->jv_copyID & mask) != (copyID & mask)
! && !job_still_alive(job))
{
/* Free the job struct itself. */
job_free_job(job);
--- 4596,4602 ----
{
job_next = job->jv_next;
if ((job->jv_copyID & mask) != (copyID & mask)
! && !job_still_useful(job))
{
/* Free the job struct itself. */
job_free_job(job);
***************
*** 4660,4667 ****
/* Only should check if the channel has been closed, if the channel is
* open the job won't exit. */
if (job->jv_status == JOB_STARTED && job->jv_exit_cb != NULL
! && (job->jv_channel == NULL
! || !channel_still_useful(job->jv_channel)))
return TRUE;
return FALSE;
}
--- 4690,4696 ----
/* Only should check if the channel has been closed, if the channel is
* open the job won't exit. */
if (job->jv_status == JOB_STARTED && job->jv_exit_cb != NULL
! && !job_channel_still_useful(job))
return TRUE;
return FALSE;
}
***************
*** 4676,4689 ****
{
int i;
for (i = 0; i < MAX_CHECK_ENDED; ++i)
{
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)
--- 4705,4722 ----
{
int i;
+ if (first_job == NULL)
+ return;
+
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);
if (job == NULL)
break;
! job_cleanup(job); /* may free "job" */
}
if (channel_need_redraw)
***************
*** 4897,4903 ****
{
char *result;
! if (job->jv_status == JOB_ENDED)
/* No need to check, dead is dead. */
result = "dead";
else if (job->jv_status == JOB_FAILED)
--- 4930,4936 ----
{
char *result;
! if (job->jv_status >= JOB_ENDED)
/* No need to check, dead is dead. */
result = "dead";
else if (job->jv_status == JOB_FAILED)
*** ../vim-8.0.0086/src/eval.c 2016-11-14 21:49:57.080090226 +0100
--- src/eval.c 2016-11-17 16:09:44.478455636 +0100
***************
*** 7290,7296 ****
if (job == NULL)
return (char_u *)"no process";
status = job->jv_status == JOB_FAILED ? "fail"
! : job->jv_status == JOB_ENDED ? "dead"
: "run";
# ifdef UNIX
vim_snprintf((char *)buf, NUMBUFLEN,
--- 7290,7296 ----
if (job == NULL)
return (char_u *)"no process";
status = job->jv_status == JOB_FAILED ? "fail"
! : job->jv_status >= JOB_ENDED ? "dead"
: "run";
# ifdef UNIX
vim_snprintf((char *)buf, NUMBUFLEN,
*** ../vim-8.0.0086/src/os_unix.c 2016-11-12 21:12:48.538182233 +0100
--- src/os_unix.c 2016-11-17 16:09:44.478455636 +0100
***************
*** 5354,5360 ****
return "run";
return_dead:
! if (job->jv_status != JOB_ENDED)
{
ch_log(job->jv_channel, "Job ended");
job->jv_status = JOB_ENDED;
--- 5354,5360 ----
return "run";
return_dead:
! if (job->jv_status < JOB_ENDED)
{
ch_log(job->jv_channel, "Job ended");
job->jv_status = JOB_ENDED;
***************
*** 5398,5404 ****
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;
--- 5398,5404 ----
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;
*** ../vim-8.0.0086/src/os_win32.c 2016-10-29 14:54:56.628135821 +0200
--- src/os_win32.c 2016-11-17 16:09:44.478455636 +0100
***************
*** 4978,4984 ****
|| dwExitCode != STILL_ACTIVE)
{
job->jv_exitval = (int)dwExitCode;
! if (job->jv_status != JOB_ENDED)
{
ch_log(job->jv_channel, "Job ended");
job->jv_status = JOB_ENDED;
--- 4978,4984 ----
|| dwExitCode != STILL_ACTIVE)
{
job->jv_exitval = (int)dwExitCode;
! if (job->jv_status < JOB_ENDED)
{
ch_log(job->jv_channel, "Job ended");
job->jv_status = JOB_ENDED;
*** ../vim-8.0.0086/src/structs.h 2016-11-10 20:20:01.870602701 +0100
--- src/structs.h 2016-11-17 16:17:49.727271673 +0100
***************
*** 1421,1431 ****
dict_T *pt_dict; /* dict for "self" */
};
typedef enum
{
JOB_FAILED,
JOB_STARTED,
! JOB_ENDED
} jobstatus_T;
/*
--- 1421,1433 ----
dict_T *pt_dict; /* dict for "self" */
};
+ /* Status of a job. Order matters! */
typedef enum
{
JOB_FAILED,
JOB_STARTED,
! JOB_ENDED, /* detected job done */
! JOB_FINISHED /* job done and cleanup done */
} jobstatus_T;
/*
*** ../vim-8.0.0086/src/testdir/test_channel.vim 2016-10-27
20:00:03.665357405 +0200
--- src/testdir/test_channel.vim 2016-11-17 16:07:29.307354887 +0100
***************
*** 1232,1237 ****
--- 1232,1263 ----
endtry
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 = ''
*** ../vim-8.0.0086/src/version.c 2016-11-15 21:16:46.754453019 +0100
--- src/version.c 2016-11-17 16:10:21.326213761 +0100
***************
*** 766,767 ****
--- 766,769 ----
{ /* Add new patch number below this line */
+ /**/
+ 87,
/**/
--
The technology involved in making anything invisible is so infinitely
complex that nine hundred and ninety-nine billion, nine hundred and
ninety-nine million, nine hundred and ninety-nine thousand, nine hundred
and ninety-nine times out of a trillion it is much simpler and more
effective just to take the thing away and do without it.
-- Douglas Adams, "The Hitchhiker's Guide to the Galaxy"
/// Bram Moolenaar -- [email protected] -- http://www.Moolenaar.net \\\
/// sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ \\\
\\\ an exciting new programming language -- http://www.Zimbu.org ///
\\\ help me help AIDS victims -- http://ICCF-Holland.org ///
--
--
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php
---
You received this message because you are subscribed to the Google Groups
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
For more options, visit https://groups.google.com/d/optout.